Commit eb40f70e authored by Christian Schulte zu Berge's avatar Christian Schulte zu Berge
Browse files

Completely moved the image data handle and histogram computation from the TF...

Completely moved the image data handle and histogram computation from the TF to the TF property. Histogram computation is now thread safe.
parent 15694cc9
...@@ -241,7 +241,7 @@ namespace campvis { ...@@ -241,7 +241,7 @@ namespace campvis {
_lblTimestamp->setText("Timestamp: " + QString::number(handles.front().second.getTimestamp())); _lblTimestamp->setText("Timestamp: " + QString::number(handles.front().second.getTimestamp()));
if (const ImageData* tester = dynamic_cast<const ImageData*>(handles.front().second.getData())) { if (const ImageData* tester = dynamic_cast<const ImageData*>(handles.front().second.getData())) {
_canvas->p_transferFunction.getTF()->setImageHandle(handles.front().second); _canvas->p_transferFunction.setImageHandle(handles.front().second);
std::ostringstream ss; std::ostringstream ss;
ss << tester->getSize(); ss << tester->getSize();
...@@ -296,7 +296,7 @@ namespace campvis { ...@@ -296,7 +296,7 @@ namespace campvis {
_lblName->setText(QString::number(handles.size()) + " DataHandles selected"); _lblName->setText(QString::number(handles.size()) + " DataHandles selected");
_lblTimestamp->setText("Timestamp: n/a"); _lblTimestamp->setText("Timestamp: n/a");
_canvas->p_transferFunction.getTF()->setImageHandle(DataHandle(0)); _canvas->p_transferFunction.setImageHandle(DataHandle(0));
} }
_lblLocalMemoryFootprint->setText("Local Memory Footprint: " + humanizeBytes(_localFootprint)); _lblLocalMemoryFootprint->setText("Local Memory Footprint: " + humanizeBytes(_localFootprint));
_lblVideoMemoryFootprint->setText("Video Memory Footprint: " + humanizeBytes(_videoFootprint)); _lblVideoMemoryFootprint->setText("Video Memory Footprint: " + humanizeBytes(_videoFootprint));
......
...@@ -24,11 +24,13 @@ ...@@ -24,11 +24,13 @@
#include "abstracttransferfunctioneditor.h" #include "abstracttransferfunctioneditor.h"
#include "core/classification/abstracttransferfunction.h" #include "core/classification/abstracttransferfunction.h"
#include "core/properties/transferfunctionproperty.h"
namespace campvis { namespace campvis {
AbstractTransferFunctionEditor::AbstractTransferFunctionEditor(AbstractTransferFunction* tf, QWidget* parent /*= 0*/) AbstractTransferFunctionEditor::AbstractTransferFunctionEditor(TransferFunctionProperty* prop, AbstractTransferFunction* tf, QWidget* parent /*= 0*/)
: QWidget(parent) : QWidget(parent)
, _tfProperty(prop)
, _transferFunction(tf) , _transferFunction(tf)
{ {
_ignorePropertyUpdates = 0; _ignorePropertyUpdates = 0;
...@@ -43,4 +45,9 @@ namespace campvis { ...@@ -43,4 +45,9 @@ namespace campvis {
if (_ignorePropertyUpdates == 0) if (_ignorePropertyUpdates == 0)
updateWidgetFromProperty(); updateWidgetFromProperty();
} }
const TransferFunctionProperty::IntensityHistogramType* AbstractTransferFunctionEditor::getIntensityHistogram() const {
return _tfProperty->getIntensityHistogram();
}
} }
\ No newline at end of file
...@@ -26,6 +26,9 @@ ...@@ -26,6 +26,9 @@
#define ABSTRACTTRANSFERFUNCTIONEDITOR_H__ #define ABSTRACTTRANSFERFUNCTIONEDITOR_H__
#include "sigslot/sigslot.h" #include "sigslot/sigslot.h"
#include "core/properties/transferfunctionproperty.h"
#include <QBoxLayout> #include <QBoxLayout>
#include <QLabel> #include <QLabel>
#include <QWidget> #include <QWidget>
...@@ -43,10 +46,11 @@ namespace campvis { ...@@ -43,10 +46,11 @@ namespace campvis {
public: public:
/** /**
* Creates a new editor widget for the for the AbstractTransferFunction \a tf. * Creates a new editor widget for the for the AbstractTransferFunction \a tf.
* \param prop TransferFunctionProperty to generate the editor for.
* \param tf The transfer function the editor shall handle. * \param tf The transfer function the editor shall handle.
* \param parent Parent Qt widget * \param parent Parent Qt widget
*/ */
AbstractTransferFunctionEditor(AbstractTransferFunction* tf, QWidget* parent = 0); AbstractTransferFunctionEditor(TransferFunctionProperty* prop, AbstractTransferFunction* tf, QWidget* parent = 0);
/** /**
* Destructor * Destructor
...@@ -59,6 +63,13 @@ namespace campvis { ...@@ -59,6 +63,13 @@ namespace campvis {
*/ */
virtual void updateWidgetFromProperty() = 0; virtual void updateWidgetFromProperty() = 0;
/**
* Tries to get the intensity histogram of the image stored in the Tf's property. May return 0.
* \return The intensity histogram of the image stored in the TF's property's data handle.
*/
const TransferFunctionProperty::IntensityHistogramType* getIntensityHistogram() const;
TransferFunctionProperty* _tfProperty; ///< The parent TransferFunctionProperty of this editor
AbstractTransferFunction* _transferFunction; ///< The transfer function this widget handles AbstractTransferFunction* _transferFunction; ///< The transfer function this widget handles
/// Semaphore acts as flag whether the widget shall ignore incoming signals from properties being updated. /// Semaphore acts as flag whether the widget shall ignore incoming signals from properties being updated.
......
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
namespace campvis { namespace campvis {
Geometry1DTransferFunctionEditor::Geometry1DTransferFunctionEditor(Geometry1DTransferFunction* tf, QWidget* parent /*= 0*/) Geometry1DTransferFunctionEditor::Geometry1DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry1DTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(tf, parent) : AbstractTransferFunctionEditor(prop, tf, parent)
, _logScale(true) , _logScale(true)
, _layout(0) , _layout(0)
, _canvas(0) , _canvas(0)
...@@ -111,7 +111,7 @@ namespace campvis { ...@@ -111,7 +111,7 @@ namespace campvis {
LGL_ERROR; LGL_ERROR;
// render histogram if existent // render histogram if existent
const AbstractTransferFunction::IntensityHistogramType* ih = gtf->getIntensityHistogram(); const TransferFunctionProperty::IntensityHistogramType* ih = getIntensityHistogram();
if (ih != 0) { if (ih != 0) {
size_t numBuckets = ih->getNumBuckets(0); size_t numBuckets = ih->getNumBuckets(0);
if (numBuckets > 0) { if (numBuckets > 0) {
......
...@@ -56,10 +56,11 @@ namespace campvis { ...@@ -56,10 +56,11 @@ namespace campvis {
public: public:
/** /**
* Creates a new editor widget for the for the TransferFunctionProperty \a property. * Creates a new editor widget for the for the TransferFunctionProperty \a property.
* \param prop TransferFunctionProperty to generate the editor for.
* \param tf The transfer function the editor shall handle. * \param tf The transfer function the editor shall handle.
* \param parent Parent Qt widget * \param parent Parent Qt widget
*/ */
Geometry1DTransferFunctionEditor(Geometry1DTransferFunction* tf, QWidget* parent = 0); Geometry1DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry1DTransferFunction* tf, QWidget* parent = 0);
/** /**
* Destructor * Destructor
......
...@@ -45,8 +45,8 @@ ...@@ -45,8 +45,8 @@
namespace campvis { namespace campvis {
Geometry2DTransferFunctionEditor::Geometry2DTransferFunctionEditor(Geometry2DTransferFunction* tf, QWidget* parent /*= 0*/) Geometry2DTransferFunctionEditor::Geometry2DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry2DTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(tf, parent) : AbstractTransferFunctionEditor(prop, tf, parent)
, _layout(0) , _layout(0)
, _canvas(0) , _canvas(0)
, _lblIntensityLeft(0) , _lblIntensityLeft(0)
...@@ -115,7 +115,7 @@ namespace campvis { ...@@ -115,7 +115,7 @@ namespace campvis {
} }
// render histogram if existent // render histogram if existent
const AbstractTransferFunction::IntensityHistogramType* ih = gtf->getIntensityHistogram(); const TransferFunctionProperty::IntensityHistogramType* ih = getIntensityHistogram();
if (ih != 0) { if (ih != 0) {
size_t numBuckets = ih->getNumBuckets(0); size_t numBuckets = ih->getNumBuckets(0);
if (numBuckets > 0) { if (numBuckets > 0) {
......
...@@ -55,10 +55,11 @@ namespace campvis { ...@@ -55,10 +55,11 @@ namespace campvis {
public: public:
/** /**
* Creates a new editor widget for the for the TransferFunctionProperty \a property. * Creates a new editor widget for the for the TransferFunctionProperty \a property.
* \param prop TransferFunctionProperty to generate the editor for.
* \param tf The transfer function the editor shall handle. * \param tf The transfer function the editor shall handle.
* \param parent Parent Qt widget * \param parent Parent Qt widget
*/ */
Geometry2DTransferFunctionEditor(Geometry2DTransferFunction* tf, QWidget* parent = 0); Geometry2DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry2DTransferFunction* tf, QWidget* parent = 0);
/** /**
* Destructor * Destructor
......
...@@ -33,8 +33,8 @@ ...@@ -33,8 +33,8 @@
namespace campvis { namespace campvis {
SimpleTransferFunctionEditor::SimpleTransferFunctionEditor(SimpleTransferFunction* tf, QWidget* parent /*= 0*/) SimpleTransferFunctionEditor::SimpleTransferFunctionEditor(TransferFunctionProperty* prop, SimpleTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(tf, parent) : AbstractTransferFunctionEditor(prop, tf, parent)
, _layout(0) , _layout(0)
, _lblColorLeft(0) , _lblColorLeft(0)
, _lblColorRight(0) , _lblColorRight(0)
......
...@@ -44,10 +44,11 @@ namespace campvis { ...@@ -44,10 +44,11 @@ namespace campvis {
public: public:
/** /**
* Creates a new editor widget for the for the TransferFunctionProperty \a property. * Creates a new editor widget for the for the TransferFunctionProperty \a property.
* \param prop TransferFunctionProperty to generate the editor for.
* \param tf The transfer function the editor shall handle. * \param tf The transfer function the editor shall handle.
* \param parent Parent Qt widget * \param parent Parent Qt widget
*/ */
SimpleTransferFunctionEditor(SimpleTransferFunction* tf, QWidget* parent = 0); SimpleTransferFunctionEditor(TransferFunctionProperty* prop, SimpleTransferFunction* tf, QWidget* parent = 0);
/** /**
* Destructor * Destructor
......
...@@ -34,21 +34,26 @@ ...@@ -34,21 +34,26 @@
#include "core/classification/geometry2dtransferfunction.h" #include "core/classification/geometry2dtransferfunction.h"
#include "core/classification/simpletransferfunction.h" #include "core/classification/simpletransferfunction.h"
#include "core/properties/transferfunctionproperty.h"
namespace campvis { namespace campvis {
AbstractTransferFunctionEditor* TransferFunctionEditorFactory::createEditor(AbstractTransferFunction* tf) { AbstractTransferFunctionEditor* TransferFunctionEditorFactory::createEditor(TransferFunctionProperty* prop) {
tgtAssert(prop != 0, "Property must not be 0.");
AbstractTransferFunction* tf = prop->getTF();
tgtAssert(tf != 0, "Transfer function must not be 0."); tgtAssert(tf != 0, "Transfer function must not be 0.");
if (SimpleTransferFunction* tester = dynamic_cast<SimpleTransferFunction*>(tf)) { if (SimpleTransferFunction* tester = dynamic_cast<SimpleTransferFunction*>(tf)) {
return new SimpleTransferFunctionEditor(tester); return new SimpleTransferFunctionEditor(prop, tester);
} }
if (Geometry1DTransferFunction* tester = dynamic_cast<Geometry1DTransferFunction*>(tf)) { if (Geometry1DTransferFunction* tester = dynamic_cast<Geometry1DTransferFunction*>(tf)) {
return new Geometry1DTransferFunctionEditor(tester); return new Geometry1DTransferFunctionEditor(prop, tester);
} }
if (Geometry2DTransferFunction* tester = dynamic_cast<Geometry2DTransferFunction*>(tf)) { if (Geometry2DTransferFunction* tester = dynamic_cast<Geometry2DTransferFunction*>(tf)) {
return new Geometry2DTransferFunctionEditor(tester); return new Geometry2DTransferFunctionEditor(prop, tester);
} }
return 0; return 0;
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
namespace campvis { namespace campvis {
class AbstractTransferFunction; class AbstractTransferFunction;
class AbstractTransferFunctionEditor; class AbstractTransferFunctionEditor;
class TransferFunctionProperty;
/** /**
* Factory class offering the static method createEditor to create transfer function editors * Factory class offering the static method createEditor to create transfer function editors
...@@ -38,10 +39,10 @@ namespace campvis { ...@@ -38,10 +39,10 @@ namespace campvis {
/** /**
* Creates the corresponding TransferFunctionEditor for the given transfer function \a tf. * Creates the corresponding TransferFunctionEditor for the given transfer function \a tf.
* \note The callee has to take the ownership of the returned pointer. * \note The callee has to take the ownership of the returned pointer.
* \param tf Transfer function to generate the editor for. * \param prop TransferFunctionProperty to generate the editor for.
* \return A new transfer function editor for the given transfer function (depending on its type). * \return A new transfer function editor for the given transfer function (depending on its type).
*/ */
static AbstractTransferFunctionEditor* createEditor(AbstractTransferFunction* tf); static AbstractTransferFunctionEditor* createEditor(TransferFunctionProperty* prop);
private: private:
/// Shall not instantiate /// Shall not instantiate
......
...@@ -130,7 +130,7 @@ namespace campvis { ...@@ -130,7 +130,7 @@ namespace campvis {
void TransferFunctionPropertyWidget::onEditClicked(bool checked) { void TransferFunctionPropertyWidget::onEditClicked(bool checked) {
if (_editor == 0) { if (_editor == 0) {
TransferFunctionProperty* prop = static_cast<TransferFunctionProperty*>(_property); TransferFunctionProperty* prop = static_cast<TransferFunctionProperty*>(_property);
_editor = TransferFunctionEditorFactory::createEditor(prop->getTF()); _editor = TransferFunctionEditorFactory::createEditor(prop);
_dockWidget = new QDockWidget("Transfer Function Editor"); _dockWidget = new QDockWidget("Transfer Function Editor");
_dockWidget->setWidget(_editor); _dockWidget->setWidget(_editor);
...@@ -145,7 +145,7 @@ namespace campvis { ...@@ -145,7 +145,7 @@ namespace campvis {
TransferFunctionProperty* prop = static_cast<TransferFunctionProperty*>(_property); TransferFunctionProperty* prop = static_cast<TransferFunctionProperty*>(_property);
AbstractTransferFunction* tf = prop->getTF(); AbstractTransferFunction* tf = prop->getTF();
DataHandle dh = tf->getImageHandle(); DataHandle dh = prop->getImageHandle();
if (dh.getData() != 0) { if (dh.getData() != 0) {
const ImageRepresentationLocal* idl = static_cast<const ImageData*>(dh.getData())->getRepresentation<ImageRepresentationLocal>(); const ImageRepresentationLocal* idl = static_cast<const ImageData*>(dh.getData())->getRepresentation<ImageRepresentationLocal>();
if (idl != 0) { if (idl != 0) {
......
...@@ -41,23 +41,18 @@ namespace campvis { ...@@ -41,23 +41,18 @@ namespace campvis {
: _size(size) : _size(size)
, _intensityDomain(intensityDomain) , _intensityDomain(intensityDomain)
, _texture(0) , _texture(0)
, _imageHandle(0)
, _intensityHistogram(0)
{ {
_dirtyTexture = false; _dirtyTexture = false;
_dirtyHistogram = false;
} }
AbstractTransferFunction::~AbstractTransferFunction() { AbstractTransferFunction::~AbstractTransferFunction() {
if (_texture != 0) if (_texture != 0)
LWARNING("Called AbstractTransferFunction dtor without proper deinitialization - you just wasted resources!"); LWARNING("Called AbstractTransferFunction dtor without proper deinitialization - you just wasted resources!");
delete _intensityHistogram;
} }
void AbstractTransferFunction::deinit() { void AbstractTransferFunction::deinit() {
delete _texture; delete _texture;
_texture = 0; _texture = 0;
_imageHandle = DataHandle(0);
} }
void AbstractTransferFunction::bind(tgt::Shader* shader, const tgt::TextureUnit& texUnit, const std::string& transFuncUniform /*= "_transferFunction"*/, const std::string& transFuncParamsUniform /*= "_transferFunctionParameters"*/) { void AbstractTransferFunction::bind(tgt::Shader* shader, const tgt::TextureUnit& texUnit, const std::string& transFuncUniform /*= "_transferFunction"*/, const std::string& transFuncParamsUniform /*= "_transferFunctionParameters"*/) {
...@@ -109,7 +104,7 @@ namespace campvis { ...@@ -109,7 +104,7 @@ namespace campvis {
tbb::mutex::scoped_lock lock(_localMutex); tbb::mutex::scoped_lock lock(_localMutex);
_intensityDomain = newDomain; _intensityDomain = newDomain;
} }
_dirtyHistogram = true; s_intensityDomainChanged();
s_changed(); s_changed();
} }
...@@ -127,49 +122,5 @@ namespace campvis { ...@@ -127,49 +122,5 @@ namespace campvis {
} }
return _texture; return _texture;
} }
DataHandle AbstractTransferFunction::getImageHandle() const {
return _imageHandle;
}
void AbstractTransferFunction::setImageHandle(DataHandle imageHandle) {
tgtAssert(
imageHandle.getData() == 0 || dynamic_cast<const ImageData*>(imageHandle.getData()) != 0,
"The data in the image handle must either be 0 or point to a valid ImageData object!");
_imageHandle = imageHandle;
_dirtyHistogram = true;
s_imageHandleChanged();
}
void AbstractTransferFunction::computeIntensityHistogram() const {
delete _intensityHistogram;
_intensityHistogram = 0;
ImageRepresentationLocal::ScopedRepresentation repLocal(_imageHandle);
if (repLocal != 0) {
float mins = _intensityDomain.x;
float maxs = _intensityDomain.y;
size_t numBuckets = std::min(WeaklyTypedPointer::numBytes(repLocal->getWeaklyTypedPointer()._baseType) << 8, static_cast<size_t>(512));
_intensityHistogram = new IntensityHistogramType(&mins, &maxs, &numBuckets);
tbb::parallel_for(tbb::blocked_range<size_t>(0, repLocal->getNumElements()), [&] (const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i != range.end(); ++i) {
float value = repLocal->getElementNormalized(i, 0);
_intensityHistogram->addSample(&value);
}
});
}
_dirtyHistogram = false;
}
const AbstractTransferFunction::IntensityHistogramType* AbstractTransferFunction::getIntensityHistogram() const {
if (_dirtyHistogram) {
computeIntensityHistogram();
}
return _intensityHistogram;
}
} }
\ No newline at end of file
...@@ -125,34 +125,11 @@ namespace campvis { ...@@ -125,34 +125,11 @@ namespace campvis {
* \return _texture * \return _texture
*/ */
const tgt::Texture* getTexture(); const tgt::Texture* getTexture();
/**
* Returns a DataHandle to the image for this transfer function, its pointer may be 0.
* \note If the data in \a imageHandle is not 0, it points to a valid ImageData object.
* \return _imageHandle, its pointer may be 0.
*/
DataHandle getImageHandle() const;
/**
* Sets the DataHandle for this transfer function, its pointer may be 0.
* \note If the data in \a imageHandle is not 0, it must point to a valid ImageData object.
* \param imageHandle The new DataHandle for this transfer function, if its pointer is
* not 0 it must point to a valid ImageData object.
*/
void setImageHandle(DataHandle imageHandle);
/**
* Returns the intensity histogram
* \todo This is NOT thread-safe!
* \return _intensityHistogram
*/
const IntensityHistogramType* getIntensityHistogram() const;
/// Signal emitted when transfer function has changed. /// Signal emitted when transfer function has changed.
sigslot::signal0<> s_changed; sigslot::signal0<> s_changed;
/// Signal emitted when the intensity domain has changed
/// Signal emitted when the image DataHandle for this TF has changed. sigslot::signal0<> s_intensityDomainChanged;
sigslot::signal0<> s_imageHandleChanged;
protected: protected:
/** /**
...@@ -171,11 +148,7 @@ namespace campvis { ...@@ -171,11 +148,7 @@ namespace campvis {
tgt::Texture* _texture; ///< OpenGL lookup texture storing the TF tgt::Texture* _texture; ///< OpenGL lookup texture storing the TF
tbb::atomic<bool> _dirtyTexture; ///< Flag whether the OpenGL texture has to be updated tbb::atomic<bool> _dirtyTexture; ///< Flag whether the OpenGL texture has to be updated
DataHandle _imageHandle; ///< DataHandle to the image for this transfer function. May be 0. mutable tbb::mutex _localMutex; ///< mutex protecting the local members
mutable IntensityHistogramType* _intensityHistogram; ///< Intensity histogram of the intensity in _imageHandle for the current _intensityDomain
mutable tbb::atomic<bool> _dirtyHistogram; ///< Flag whether the intensity histogram has to be updated.
mutable tbb::mutex _localMutex; ///< mutex protecting the local members
static const std::string loggerCat_; static const std::string loggerCat_;
......
...@@ -46,13 +46,12 @@ namespace campvis { ...@@ -46,13 +46,12 @@ namespace campvis {
ImageRepresentationLocal::ImageRepresentationLocal(ImageData* parent, WeaklyTypedPointer::BaseType baseType) ImageRepresentationLocal::ImageRepresentationLocal(ImageData* parent, WeaklyTypedPointer::BaseType baseType)
: GenericAbstractImageRepresentation<ImageRepresentationLocal>(parent) : GenericAbstractImageRepresentation<ImageRepresentationLocal>(parent)
, _baseType(baseType) , _baseType(baseType)
, _intensityHistogram(0)
{ {
_intensityRangeDirty = true; _intensityRangeDirty = true;
} }
ImageRepresentationLocal::~ImageRepresentationLocal() { ImageRepresentationLocal::~ImageRepresentationLocal() {
delete _intensityHistogram;
} }
ImageRepresentationLocal* ImageRepresentationLocal::tryConvertFrom(const AbstractImageRepresentation* source) { ImageRepresentationLocal* ImageRepresentationLocal::tryConvertFrom(const AbstractImageRepresentation* source) {
...@@ -145,13 +144,6 @@ namespace campvis { ...@@ -145,13 +144,6 @@ namespace campvis {
return _normalizedIntensityRange; return _normalizedIntensityRange;
} }
const ConcurrentGenericHistogramND<float, 1>& ImageRepresentationLocal::getIntensityHistogram() const {
if (_intensityHistogram == 0)
computeIntensityHistogram();
return *_intensityHistogram;
}
void ImageRepresentationLocal::computeNormalizedIntensityRange() const { void ImageRepresentationLocal::computeNormalizedIntensityRange() const {
_normalizedIntensityRange = Interval<float>(); // reset interval to empty one _normalizedIntensityRange = Interval<float>(); // reset interval to empty one
tbb::spin_mutex _mutex; // mutex to protect for concurrent access tbb::spin_mutex _mutex; // mutex to protect for concurrent access
...@@ -177,23 +169,6 @@ namespace campvis { ...@@ -177,23 +169,6 @@ namespace campvis {
_intensityRangeDirty = false; _intensityRangeDirty = false;
} }
void ImageRepresentationLocal::computeIntensityHistogram() const {
delete _intensityHistogram;
const Interval<float>& i = getNormalizedIntensityRange();
float mins = i.getLeft();
float maxs = i.getRight();
size_t numBuckets = 1024;
_intensityHistogram = new IntensityHistogramType(&mins, &maxs, &numBuckets);