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 {
_lblTimestamp->setText("Timestamp: " + QString::number(handles.front().second.getTimestamp()));
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;
ss << tester->getSize();
......@@ -296,7 +296,7 @@ namespace campvis {
_lblName->setText(QString::number(handles.size()) + " DataHandles selected");
_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));
_lblVideoMemoryFootprint->setText("Video Memory Footprint: " + humanizeBytes(_videoFootprint));
......
......@@ -24,11 +24,13 @@
#include "abstracttransferfunctioneditor.h"
#include "core/classification/abstracttransferfunction.h"
#include "core/properties/transferfunctionproperty.h"
namespace campvis {
AbstractTransferFunctionEditor::AbstractTransferFunctionEditor(AbstractTransferFunction* tf, QWidget* parent /*= 0*/)
AbstractTransferFunctionEditor::AbstractTransferFunctionEditor(TransferFunctionProperty* prop, AbstractTransferFunction* tf, QWidget* parent /*= 0*/)
: QWidget(parent)
, _tfProperty(prop)
, _transferFunction(tf)
{
_ignorePropertyUpdates = 0;
......@@ -43,4 +45,9 @@ namespace campvis {
if (_ignorePropertyUpdates == 0)
updateWidgetFromProperty();
}
const TransferFunctionProperty::IntensityHistogramType* AbstractTransferFunctionEditor::getIntensityHistogram() const {
return _tfProperty->getIntensityHistogram();
}
}
\ No newline at end of file
......@@ -26,6 +26,9 @@
#define ABSTRACTTRANSFERFUNCTIONEDITOR_H__
#include "sigslot/sigslot.h"
#include "core/properties/transferfunctionproperty.h"
#include <QBoxLayout>
#include <QLabel>
#include <QWidget>
......@@ -43,10 +46,11 @@ namespace campvis {
public:
/**
* 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 parent Parent Qt widget
*/
AbstractTransferFunctionEditor(AbstractTransferFunction* tf, QWidget* parent = 0);
AbstractTransferFunctionEditor(TransferFunctionProperty* prop, AbstractTransferFunction* tf, QWidget* parent = 0);
/**
* Destructor
......@@ -59,6 +63,13 @@ namespace campvis {
*/
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
/// Semaphore acts as flag whether the widget shall ignore incoming signals from properties being updated.
......
......@@ -46,8 +46,8 @@
namespace campvis {
Geometry1DTransferFunctionEditor::Geometry1DTransferFunctionEditor(Geometry1DTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(tf, parent)
Geometry1DTransferFunctionEditor::Geometry1DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry1DTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(prop, tf, parent)
, _logScale(true)
, _layout(0)
, _canvas(0)
......@@ -111,7 +111,7 @@ namespace campvis {
LGL_ERROR;
// render histogram if existent
const AbstractTransferFunction::IntensityHistogramType* ih = gtf->getIntensityHistogram();
const TransferFunctionProperty::IntensityHistogramType* ih = getIntensityHistogram();
if (ih != 0) {
size_t numBuckets = ih->getNumBuckets(0);
if (numBuckets > 0) {
......
......@@ -56,10 +56,11 @@ namespace campvis {
public:
/**
* 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 parent Parent Qt widget
*/
Geometry1DTransferFunctionEditor(Geometry1DTransferFunction* tf, QWidget* parent = 0);
Geometry1DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry1DTransferFunction* tf, QWidget* parent = 0);
/**
* Destructor
......
......@@ -45,8 +45,8 @@
namespace campvis {
Geometry2DTransferFunctionEditor::Geometry2DTransferFunctionEditor(Geometry2DTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(tf, parent)
Geometry2DTransferFunctionEditor::Geometry2DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry2DTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(prop, tf, parent)
, _layout(0)
, _canvas(0)
, _lblIntensityLeft(0)
......@@ -115,7 +115,7 @@ namespace campvis {
}
// render histogram if existent
const AbstractTransferFunction::IntensityHistogramType* ih = gtf->getIntensityHistogram();
const TransferFunctionProperty::IntensityHistogramType* ih = getIntensityHistogram();
if (ih != 0) {
size_t numBuckets = ih->getNumBuckets(0);
if (numBuckets > 0) {
......
......@@ -55,10 +55,11 @@ namespace campvis {
public:
/**
* 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 parent Parent Qt widget
*/
Geometry2DTransferFunctionEditor(Geometry2DTransferFunction* tf, QWidget* parent = 0);
Geometry2DTransferFunctionEditor(TransferFunctionProperty* prop, Geometry2DTransferFunction* tf, QWidget* parent = 0);
/**
* Destructor
......
......@@ -33,8 +33,8 @@
namespace campvis {
SimpleTransferFunctionEditor::SimpleTransferFunctionEditor(SimpleTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(tf, parent)
SimpleTransferFunctionEditor::SimpleTransferFunctionEditor(TransferFunctionProperty* prop, SimpleTransferFunction* tf, QWidget* parent /*= 0*/)
: AbstractTransferFunctionEditor(prop, tf, parent)
, _layout(0)
, _lblColorLeft(0)
, _lblColorRight(0)
......
......@@ -44,10 +44,11 @@ namespace campvis {
public:
/**
* 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 parent Parent Qt widget
*/
SimpleTransferFunctionEditor(SimpleTransferFunction* tf, QWidget* parent = 0);
SimpleTransferFunctionEditor(TransferFunctionProperty* prop, SimpleTransferFunction* tf, QWidget* parent = 0);
/**
* Destructor
......
......@@ -34,21 +34,26 @@
#include "core/classification/geometry2dtransferfunction.h"
#include "core/classification/simpletransferfunction.h"
#include "core/properties/transferfunctionproperty.h"
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.");
if (SimpleTransferFunction* tester = dynamic_cast<SimpleTransferFunction*>(tf)) {
return new SimpleTransferFunctionEditor(tester);
return new SimpleTransferFunctionEditor(prop, tester);
}
if (Geometry1DTransferFunction* tester = dynamic_cast<Geometry1DTransferFunction*>(tf)) {
return new Geometry1DTransferFunctionEditor(tester);
return new Geometry1DTransferFunctionEditor(prop, tester);
}
if (Geometry2DTransferFunction* tester = dynamic_cast<Geometry2DTransferFunction*>(tf)) {
return new Geometry2DTransferFunctionEditor(tester);
return new Geometry2DTransferFunctionEditor(prop, tester);
}
return 0;
......
......@@ -28,6 +28,7 @@
namespace campvis {
class AbstractTransferFunction;
class AbstractTransferFunctionEditor;
class TransferFunctionProperty;
/**
* Factory class offering the static method createEditor to create transfer function editors
......@@ -38,10 +39,10 @@ namespace campvis {
/**
* Creates the corresponding TransferFunctionEditor for the given transfer function \a tf.
* \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).
*/
static AbstractTransferFunctionEditor* createEditor(AbstractTransferFunction* tf);
static AbstractTransferFunctionEditor* createEditor(TransferFunctionProperty* prop);
private:
/// Shall not instantiate
......
......@@ -130,7 +130,7 @@ namespace campvis {
void TransferFunctionPropertyWidget::onEditClicked(bool checked) {
if (_editor == 0) {
TransferFunctionProperty* prop = static_cast<TransferFunctionProperty*>(_property);
_editor = TransferFunctionEditorFactory::createEditor(prop->getTF());
_editor = TransferFunctionEditorFactory::createEditor(prop);
_dockWidget = new QDockWidget("Transfer Function Editor");
_dockWidget->setWidget(_editor);
......@@ -145,7 +145,7 @@ namespace campvis {
TransferFunctionProperty* prop = static_cast<TransferFunctionProperty*>(_property);
AbstractTransferFunction* tf = prop->getTF();
DataHandle dh = tf->getImageHandle();
DataHandle dh = prop->getImageHandle();
if (dh.getData() != 0) {
const ImageRepresentationLocal* idl = static_cast<const ImageData*>(dh.getData())->getRepresentation<ImageRepresentationLocal>();
if (idl != 0) {
......
......@@ -41,23 +41,18 @@ namespace campvis {
: _size(size)
, _intensityDomain(intensityDomain)
, _texture(0)
, _imageHandle(0)
, _intensityHistogram(0)
{
_dirtyTexture = false;
_dirtyHistogram = false;
}
AbstractTransferFunction::~AbstractTransferFunction() {
if (_texture != 0)
LWARNING("Called AbstractTransferFunction dtor without proper deinitialization - you just wasted resources!");
delete _intensityHistogram;
}
void AbstractTransferFunction::deinit() {
delete _texture;
_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"*/) {
......@@ -109,7 +104,7 @@ namespace campvis {
tbb::mutex::scoped_lock lock(_localMutex);
_intensityDomain = newDomain;
}
_dirtyHistogram = true;
s_intensityDomainChanged();
s_changed();
}
......@@ -127,49 +122,5 @@ namespace campvis {
}
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 {
* \return _texture
*/
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.
sigslot::signal0<> s_changed;
/// Signal emitted when the image DataHandle for this TF has changed.
sigslot::signal0<> s_imageHandleChanged;
/// Signal emitted when the intensity domain has changed
sigslot::signal0<> s_intensityDomainChanged;
protected:
/**
......@@ -171,11 +148,7 @@ namespace campvis {
tgt::Texture* _texture; ///< OpenGL lookup texture storing the TF
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 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
mutable tbb::mutex _localMutex; ///< mutex protecting the local members
static const std::string loggerCat_;
......
......@@ -46,13 +46,12 @@ namespace campvis {
ImageRepresentationLocal::ImageRepresentationLocal(ImageData* parent, WeaklyTypedPointer::BaseType baseType)
: GenericAbstractImageRepresentation<ImageRepresentationLocal>(parent)
, _baseType(baseType)
, _intensityHistogram(0)
{
_intensityRangeDirty = true;
}
ImageRepresentationLocal::~ImageRepresentationLocal() {
delete _intensityHistogram;
}
ImageRepresentationLocal* ImageRepresentationLocal::tryConvertFrom(const AbstractImageRepresentation* source) {
......@@ -145,13 +144,6 @@ namespace campvis {
return _normalizedIntensityRange;
}
const ConcurrentGenericHistogramND<float, 1>& ImageRepresentationLocal::getIntensityHistogram() const {
if (_intensityHistogram == 0)
computeIntensityHistogram();
return *_intensityHistogram;
}
void ImageRepresentationLocal::computeNormalizedIntensityRange() const {
_normalizedIntensityRange = Interval<float>(); // reset interval to empty one
tbb::spin_mutex _mutex; // mutex to protect for concurrent access
......@@ -177,23 +169,6 @@ namespace campvis {
_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);
tbb::parallel_for(tbb::blocked_range<size_t>(0, getNumElements()), [&] (const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i != range.end(); ++i) {
float value = this->getElementNormalized(i, 0);
_intensityHistogram->addSample(&value);
}
});
}
ImageRepresentationLocal* ImageRepresentationLocal::create(const ImageData* parent, WeaklyTypedPointer wtp) {
#define CONVERT_DISK_TO_GENERIC_LOCAL(baseType,numChannels) \
return GenericImageRepresentationLocal<baseType, numChannels>::create( \
......
......@@ -42,8 +42,6 @@ namespace campvis {
*/
class ImageRepresentationLocal : public GenericAbstractImageRepresentation<ImageRepresentationLocal> {
public:
typedef ConcurrentGenericHistogramND<float, 1> IntensityHistogramType;
/**
* Destructor
*/
......@@ -169,14 +167,6 @@ namespace campvis {
*/
const Interval<float>& getNormalizedIntensityRange() const;
/**
* Returns the intensity distribution normalized to float as 1D histogram.
* \note The intensity histogram is computed using lazy evaluation, hence, computation
* may take some time.
* \return _intensityHistogram
*/
const IntensityHistogramType& getIntensityHistogram() const;
protected:
/**
* Creates a new ImageData representation in local memory.
......@@ -192,16 +182,11 @@ namespace campvis {
*/
void computeNormalizedIntensityRange() const;
/**
* Computes the intensity histogram.
*/
void computeIntensityHistogram() const;
WeaklyTypedPointer::BaseType _baseType; ///< Base type of the image data
mutable tbb::atomic<bool> _intensityRangeDirty; ///< Flag whether _normalizedIntensityRange is dirty and has to be recomputed
mutable Interval<float> _normalizedIntensityRange; ///< Range of the normalized intensities, mutable to allow lazy instantiation
mutable IntensityHistogramType* _intensityHistogram; ///< Intensity histogram, mutable to allow lazy instantiation
static const std::string loggerCat_;
......
......@@ -86,7 +86,7 @@ namespace campvis {
if (img.getDataHandle().getTimestamp() != _sourceImageTimestamp) {
// source DataHandle has changed
_sourceImageTimestamp = img.getDataHandle().getTimestamp();
p_transferFunction.getTF()->setImageHandle(img.getDataHandle());
p_transferFunction.setImageHandle(img.getDataHandle());
}
if (hasInvalidShader()) {
......
......@@ -26,6 +26,8 @@
#include "core/datastructures/imagedata.h"
#include "core/datastructures/imagerepresentationlocal.h"
#include <tbb/tbb.h>
namespace campvis {
const std::string TransferFunctionProperty::loggerCat_ = "CAMPVis.core.datastructures.TransferFunctionProperty";
......@@ -37,12 +39,17 @@ namespace campvis {
, _autoFitWindowToData(true)
{
tgtAssert(tf != 0, "Assigned transfer function must not be 0.");
tf->s_changed.connect(this, &TransferFunctionProperty::onTFChanged);
tf->s_intensityDomainChanged.connect(this, &TransferFunctionProperty::onTfIntensityDomainChanged);
_intensityHistogram = 0;
_dirtyHistogram = false;
}
TransferFunctionProperty::~TransferFunctionProperty() {
_transferFunction->s_changed.disconnect(this);
delete _transferFunction;
delete _intensityHistogram;
}
AbstractTransferFunction* TransferFunctionProperty::getTF() {
......@@ -54,6 +61,8 @@ namespace campvis {
}
void TransferFunctionProperty::deinit() {
_transferFunction->s_changed.disconnect(this);
_transferFunction->s_intensityDomainChanged.disconnect(this);
_transferFunction->deinit();
_imageHandle = DataHandle(0);
}
......@@ -64,14 +73,17 @@ namespace campvis {
if (_transferFunction != 0) {
_transferFunction->s_changed.disconnect(this);
_transferFunction->s_intensityDomainChanged.disconnect(this);
_transferFunction->deinit();
}
delete _transferFunction;
_transferFunction = tf;
if (_transferFunction != 0)
if (_transferFunction != 0) {
_transferFunction->s_changed.connect(this, &TransferFunctionProperty::onTFChanged);
_transferFunction->s_intensityDomainChanged.connect(this, &TransferFunctionProperty::onTfIntensityDomainChanged);
}
s_AfterTFReplace(_transferFunction);
}
......@@ -100,6 +112,7 @@ namespace campvis {
}
_imageHandle = imageHandle;
_dirtyHistogram = true;
s_imageHandleChanged();
}
......@@ -112,4 +125,40 @@ namespace campvis {
return _autoFitWindowToData;
}
void TransferFunctionProperty::computeIntensityHistogram() const {
IntensityHistogramType* newHistogram = 0;
// create new histogram according to the current intensity domain
ImageRepresentationLocal::ScopedRepresentation repLocal(_imageHandle);
if (repLocal != 0) {
float mins = _transferFunction->getIntensityDomain().x;
float maxs = _transferFunction->getIntensityDomain().y;
size_t numBuckets = std::min(WeaklyTypedPointer::numBytes(repLocal->getWeaklyTypedPointer()._baseType) << 8, static_cast<size_t>(512));
newHistogram = 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);
newHistogram->addSample(&value);
}
});
}
// atomically replace old histogram with the new one and delete the old one.
IntensityHistogramType* oldHistogram = _intensityHistogram.fetch_and_store(newHistogram);
delete oldHistogram;
_dirtyHistogram = false;
}
const TransferFunctionProperty::IntensityHistogramType* TransferFunctionProperty::getIntensityHistogram() const {
if (_dirtyHistogram) {
computeIntensityHistogram();
}
return _intensityHistogram;
}
void TransferFunctionProperty::onTfIntensityDomainChanged() {
_dirtyHistogram = true;
}
}
......@@ -29,6 +29,8 @@
#include "core/properties/abstractproperty.h"
#include "core/classification/abstracttransferfunction.h"
#include <tbb/atomic.h>