Currently job artifacts in CI/CD pipelines on LRZ GitLab never expire. Starting from Wed 26.1.2022 the default expiration time will be 30 days (GitLab default). Currently existing artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit caa7620d authored by schultezub's avatar schultezub
Browse files

=== ATTENTION: CAMPVis does compile, but image representation conversion not...

=== ATTENTION: CAMPVis does compile, but image representation conversion not yet functional again! ===

Revising ImageData concept step 2:
 * Removed all ImageDataXYZ classes
 * Introducing GenericAbstractImageRepresentation<T> and its GenericAbstractImageRepresentation<T>::ScopedRepresentation
 * Updated all processors and pipelines to use ImageRepresentationXYZ instead of ImageDataXYZ and the implicit conversion feature

git-svn-id: https://camplinux.in.tum.de/svn/campvis/trunk@401 bb408c1c-ae56-11e1-83d9-df6b3e0c105e
parent 670e632c
......@@ -28,6 +28,7 @@
// ================================================================================================
#include "application/campvisapplication.h"
#include "modules/vis/pipelines/advancedusvis.h"
#include "modules/vis/pipelines/ixpvdemo.h"
#include "modules/vis/pipelines/dvrvis.h"
#include "modules/vis/pipelines/slicevis.h"
......@@ -44,7 +45,8 @@ using namespace campvis;
**/
int main(int argc, char** argv) {
CampVisApplication app(argc, argv);
app.addVisualizationPipeline("IXPV", new IxpvDemo());
app.addVisualizationPipeline("Advanced Ultrasound Visualization", new AdvancedUsVis());
//app.addVisualizationPipeline("IXPV", new IxpvDemo());
//app.addVisualizationPipeline("SliceVis", new SliceVis());
//app.addVisualizationPipeline("DVRVis", new DVRVis());
//app.addVisualizationPipeline("DVR with OpenCL", new OpenCLPipeline());
......
......@@ -37,7 +37,8 @@
#include "tgt/quadric.h"
#include "core/datastructures/imagedatarendertarget.h"
#include "core/datastructures/imagedata.h"
#include "core/datastructures/imagerepresentationrendertarget.h"
#include "core/pipeline/visualizationpipeline.h"
#include "core/tools/job.h"
#include "core/tools/opengljobprocessor.h"
......@@ -94,7 +95,7 @@ namespace campvis {
glViewport(0, 0, size.x, size.y);
// try get Data
DataContainer::ScopedTypedData<ImageDataRenderTarget> image(_pipeline->getDataContainer(), _pipeline->getRenderTargetID());
ImageRepresentationRenderTarget::ScopedRepresentation image(_pipeline->getDataContainer(), _pipeline->getRenderTargetID());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (image != 0) {
// activate shader
......
......@@ -35,8 +35,8 @@
#include "core/datastructures/datacontainer.h"
#include "core/datastructures/datahandle.h"
#include "core/datastructures/imagedatarendertarget.h"
#include "core/datastructures/imagedatagl.h"
#include "core/datastructures/imagerepresentationrendertarget.h"
#include "core/datastructures/imagerepresentationgl.h"
#include "core/datastructures/facegeometry.h"
#include "core/tools/job.h"
......@@ -140,10 +140,10 @@ namespace campvis {
std::vector<const tgt::Texture*> textures;
for (std::map<std::string, DataHandle>::iterator it = _handles.begin(); it != _handles.end(); ++it) {
if (const ImageDataGL* imgGL = dynamic_cast<const ImageDataGL*>(it->second.getData())) {
if (const ImageRepresentationGL* imgGL = dynamic_cast<const ImageRepresentationGL*>(it->second.getData())) {
textures.push_back(imgGL->getTexture());
}
else if (const ImageDataRenderTarget* imgRT = dynamic_cast<const ImageDataRenderTarget*>(it->second.getData())) {
else if (const ImageRepresentationRenderTarget* imgRT = dynamic_cast<const ImageRepresentationRenderTarget*>(it->second.getData())) {
if (imgRT->getDimensionality() == 2) {
for (size_t i = 0; i < imgRT->getNumColorTextures(); ++i)
textures.push_back(imgRT->getColorTexture(i));
......
......@@ -36,10 +36,10 @@
#include "application/gui/qtdatahandle.h"
#include "core/datastructures/datacontainer.h"
#include "core/datastructures/abstractdata.h"
#include "core/datastructures/imagedatadisk.h"
#include "core/datastructures/imagedatalocal.h"
#include "core/datastructures/imagedatarendertarget.h"
#include "core/datastructures/imagedatagl.h"
#include "core/datastructures/imagerepresentationdisk.h"
#include "core/datastructures/imagerepresentationlocal.h"
#include "core/datastructures/imagerepresentationrendertarget.h"
#include "core/datastructures/imagerepresentationgl.h"
#include <QHeaderView>
#include <QStringList>
......@@ -91,16 +91,16 @@ namespace campvis {
else if (column == COLUMN_TYPE) {
const AbstractData* data = _dataHandle.getData();
tgtAssert(data != 0, "WTF - QtDataHandle with empty data?");
if (const ImageDataDisk* tester = dynamic_cast<const ImageDataDisk*>(data)) {
if (const ImageRepresentationDisk* tester = dynamic_cast<const ImageRepresentationDisk*>(data)) {
return QVariant(QString("ImageData on disk"));
}
else if (const ImageDataLocal* tester = dynamic_cast<const ImageDataLocal*>(data)) {
else if (const ImageRepresentationLocal* tester = dynamic_cast<const ImageRepresentationLocal*>(data)) {
return QVariant(QString("ImageData in local memory"));
}
else if (const ImageDataGL* tester = dynamic_cast<const ImageDataGL*>(data)) {
else if (const ImageRepresentationGL* tester = dynamic_cast<const ImageRepresentationGL*>(data)) {
return QVariant(QString("ImageData in OpenGL texture."));
}
else if (const ImageDataRenderTarget* tester = dynamic_cast<const ImageDataRenderTarget*>(data)) {
else if (const ImageRepresentationRenderTarget* tester = dynamic_cast<const ImageRepresentationRenderTarget*>(data)) {
return QVariant(QString("RenderTarget"));
}
}
......
......@@ -39,7 +39,7 @@
#include "core/classification/geometry1dtransferfunction.h"
#include "core/classification/tfgeometry1d.h"
#include "core/datastructures/imagedatalocal.h"
#include "core/datastructures/imagerepresentationlocal.h"
#include "core/properties/transferfunctionproperty.h"
#include "core/tools/opengljobprocessor.h"
......
......@@ -39,7 +39,7 @@
#include "core/classification/geometry2dtransferfunction.h"
#include "core/classification/TFGeometry2D.h"
#include "core/datastructures/imagedatalocal.h"
#include "core/datastructures/imagerepresentationlocal.h"
#include "core/properties/transferfunctionproperty.h"
#include "core/tools/opengljobprocessor.h"
......
......@@ -31,7 +31,7 @@
#include "application/gui/properties/abstracttransferfunctioneditor.h"
#include "application/gui/properties/transferfunctioneditorfactory.h"
#include "core/datastructures/imagedatalocal.h"
#include "core/datastructures/imagerepresentationlocal.h"
#include <QDockWidget>
#include <QDoubleSpinBox>
......@@ -97,7 +97,7 @@ namespace campvis {
void TransferFunctionPropertyWidget::onTransferFunctionImageHandleChanged() {
DataHandle dh = static_cast<TransferFunctionProperty*>(_property)->getTF()->getImageHandle();
if (dh.getData() != 0) {
const ImageDataLocal* idl = dynamic_cast<const ImageDataLocal*>(dh.getData());
const ImageRepresentationLocal* idl = dynamic_cast<const ImageRepresentationLocal*>(dh.getData());
if (idl != 0) {
Interval<float> intensityInterval = idl->getNormalizedIntensityRange();
// _spinDomainLeft->setMinimum(intensityInterval.getLeft());
......@@ -150,7 +150,7 @@ namespace campvis {
DataHandle dh = tf->getImageHandle();
if (dh.getData() != 0) {
const ImageDataLocal* idl = dynamic_cast<const ImageDataLocal*>(dh.getData());
const ImageRepresentationLocal* idl = dynamic_cast<const ImageRepresentationLocal*>(dh.getData());
if (idl != 0) {
Interval<float> intensityInterval = idl->getNormalizedIntensityRange();
tf->setIntensityDomain(tgt::vec2(intensityInterval.getLeft(), intensityInterval.getRight()));
......
......@@ -36,13 +36,13 @@
#include "tgt/texture.h"
#include "tgt/textureunit.h"
#include "core/datastructures/imagedatalocal.h"
#include "core/datastructures/imagerepresentationlocal.h"
namespace campvis {
class IntensityHistogramGenerator {
public:
IntensityHistogramGenerator(const ImageDataLocal* intensityData, AbstractTransferFunction::IntensityHistogramType* histogram)
IntensityHistogramGenerator(const ImageRepresentationLocal* intensityData, AbstractTransferFunction::IntensityHistogramType* histogram)
: _intensityData(intensityData)
, _histogram(histogram)
{}
......@@ -55,7 +55,7 @@ namespace campvis {
}
protected:
const ImageDataLocal* _intensityData;
const ImageRepresentationLocal* _intensityData;
AbstractTransferFunction::IntensityHistogramType* _histogram;
};
......@@ -169,7 +169,7 @@ namespace campvis {
_intensityHistogram = 0;
if (_imageHandle.getData() != 0) {
const ImageDataLocal* idl = dynamic_cast<const ImageDataLocal*>(_imageHandle.getData());
const ImageRepresentationLocal* idl = dynamic_cast<const ImageRepresentationLocal*>(_imageHandle.getData());
if (idl != 0) {
float mins = _intensityDomain.x;
float maxs = _intensityDomain.y;
......
......@@ -106,7 +106,7 @@ namespace campvis {
* \param urb Upper-Right-Back coordinates of subimage
* \return An image representation containing the subimage of this with the given coordinates.
*/
virtual AbstractImageRepresentation* getSubImage(const tgt::svec3& llf, const tgt::svec3& urb) const = 0;
virtual AbstractImageRepresentation* getSubImage(const ImageData* parent, const tgt::svec3& llf, const tgt::svec3& urb) const = 0;
protected:
......@@ -114,7 +114,6 @@ namespace campvis {
static const std::string loggerCat_;
};
}
#endif // ABSTRACTIMAGEREPRESENTATION_H__
......@@ -58,7 +58,7 @@ namespace campvis {
public:
/**
* Proxy class for scoped strongly-typed access to the data of a DataContainer.
* From the outside ScopedTypedData<T> behaves exactly like a const T*, but internally it preserves the
* From the outside DataContainer::ScopedTypedData<T> behaves exactly like a const T*, but internally it preserves the
* reference counting of a DataHandle. Use this class when you want temporary access to a strongly-typed
* data item in a DataContainer but don't want to to the dynamic_cast yourself.
*
......
......@@ -27,118 +27,107 @@
//
// ================================================================================================
#ifndef IMAGEDATADISK_H__
#define IMAGEDATADISK_H__
#ifndef GENERICABSTRACTIMAGEREPRESENTATION_H__
#define GENERICABSTRACTIMAGEREPRESENTATION_H__
#include "tgt/vector.h"
#include "core/datastructures/abstractimagerepresentation.h"
#include "core/datastructures/datacontainer.h"
#include "core/datastructures/imagedata.h"
#include "core/tools/endianhelper.h"
#include "core/tools/typetraits.h"
#include "core/tools/weaklytypedpointer.h"
#include <fstream>
#include <string>
namespace campvis {
/**
* Subclass of ImageData offering access to image data stored in binary form on the local harddisk.
* Generic wrapper around an AbstractImageRepresentation that provides a ScopedRepresentation proxy.
* The concept is very similar to the DataContainer::ScopedData proxy.
* From the outside ScopedRepresentation behaves exactly like a const T*, but internally it preserves the
* reference counting of a DataHandle. Use this class when you want temporary access to a specific
* representation of an ImageData item in a DataContainer but don't want to do the dynamic_cast and
* the getRepresentation<T> yourself.
*
* \todo Number of channels
* \tparam T Base class of the ImageRepresentation to get.
*/
class ImageDataDisk : public ImageData {
template<typename T>
class GenericAbstractImageRepresentation : public AbstractImageRepresentation {
public:
struct ScopedRepresentation {
/**
* Creates a new ImageData disk representation.
*
* \param url Path to file with raw data
* \param dimensionality Dimensionality of data
* \param size Size of this image (number of elements per dimension)
* \param type Base type of data
* \param numChannels Number of channels per element
* \param offset Offset of first data element in file (in bytes)
* \param endianness Endianess of data
* \param stride Number of _elemments_ _between_ adjacent elements for each dimension (\see ImageDataDisk::_stride).
*/
ImageDataDisk(
const std::string& url,
size_t dimensionality,
const tgt::svec3& size,
WeaklyTypedPointer::BaseType type,
size_t numChannels,
size_t offset = 0,
EndianHelper::Endianness endianness = EndianHelper::LITTLE_ENDIAN,
const tgt::svec3& stride = tgt::svec3::zero
);
/**
* Destructor
* Creates a new DataHandle to the data item with the key \a name in \a dc, that behaves like a T*.
* \param dc DataContainer to grab data from
* \param name Key of the DataHandle to search for
*/
virtual ~ImageDataDisk();
/**
* \see AbstractData::clone()
**/
virtual ImageDataDisk* clone() const;
/// \see AbstractData::getLocalMemoryFootprint()
virtual size_t getLocalMemoryFootprint() const;
/// \see AbstractData::getVideoMemoryFootprint()
virtual size_t getVideoMemoryFootprint() const;
ScopedRepresentation(const DataContainer& dc, const std::string& name)
: dh(dc.getData(name))
, data(0)
, representation(0)
{
if (dh.getData() != 0) {
data = dynamic_cast<const ImageData*>(dh.getData());
if (data != 0) {
representation = data->getRepresentation<T>();
}
}
};
/**
* \see ImageData::getSubImage
* Implicit conversion operator to const T*.
* \return The image representation of the image in the DataHandle, may be 0 when no DataHandle was found,
* the data is of the wrong type, or no suitable representation was found.
*/
virtual ImageDataDisk* getSubImage(const tgt::svec3& llf, const tgt::svec3& urb) const;
operator const T*() {
return representation;
}
/**
* Downloads the whole image data to local memory.
* \note The caller has to take ownership of the returned pointer.
* \return Pointer to the image data in the local memory, to be owned by caller.
* Implicit arrow operator to const T*.
* \return The image representation of the image in the DataHandle, may be 0 when no DataHandle was found,
* the data is of the wrong type, or no suitable representation was found.
*/
WeaklyTypedPointer getImageData() const;
const T* operator->() const {
return representation;
}
/**
* Returns the base type of the data
* \return _type
* Gets the DataHandle.
* \return dh
*/
WeaklyTypedPointer::BaseType getBaseType() const;
DataHandle getDataHandle() const {
return dh;
}
/**
* Returns the number of channels per element
* \return _numChannels
* Returns the parent ImageData of this image representation.
* \return data
*/
size_t getNumChannels() const;
const ImageData* getImageData() const {
return data;
}
private:
/// Not copy-constructable
ScopedRepresentation(const ScopedRepresentation& rhs);
/// Not assignable
ScopedRepresentation& operator=(const ScopedRepresentation& rhs);
DataHandle dh; ///< DataHandle
const ImageData* data; ///< strongly-typed pointer to data, may be 0
const T* representation; ///< strongly-typed pointer to the image representation, may be 0
};
/**
* Calculates the canonical stride for the given image size.
* \param size Image size (number of elements per dimension).
* \return (0, size.x, size.x * size.y)
* Creates a new abstract representation for the image \a parent.
* \param parent Image this representation represents, must not be 0.
*/
tgt::svec3 getCanonicStride(const tgt::svec3& size) const;
std::string _url; ///< path to file with raw data
size_t _offset; ///< offset of first data element in file (in bytes)
WeaklyTypedPointer::BaseType _type; ///< base type of data
size_t _numChannels; ///< number of channels per element
EndianHelper::Endianness _endianess; ///< endianess of data
GenericAbstractImageRepresentation(const ImageData* parent)
: AbstractImageRepresentation(parent)
{
}
/**
* Number of _elemments_ _between_ adjacent elements for each dimension.
* If the stride is 0 for a dimension, the elements are assumed to be tightly packed, hence for
* the x-dimension 0, for the y-dimension _size.x and for the z-dimension (_size.x * _size.y).
**/
tgt::svec3 _stride;
* Virtual Destructor.
*/
virtual ~GenericAbstractImageRepresentation() {};
static const std::string loggerCat_;
};
}
#endif // IMAGEDATADISK_H__
#endif // GENERICABSTRACTIMAGEREPRESENTATION_H__
// ================================================================================================
//
// This file is part of the CAMPVis Software Framework.
//
// If not explicitly stated otherwise: Copyright (C) 2012, all rights reserved,
// Christian Schulte zu Berge <christian.szb@in.tum.de>
// Chair for Computer Aided Medical Procedures
// Technische Universität München
// Boltzmannstr. 3, 85748 Garching b. München, Germany
// For a full list of authors and contributors, please refer to the file "AUTHORS.txt".
//
// The licensing of this softare is not yet resolved. Until then, redistribution in source or
// binary forms outside the CAMP chair is not permitted, unless explicitly stated in legal form.
// However, the names of the original authors and the above copyright notice must retain in its
// original state in any case.
//
// Legal disclaimer provided by the BSD license:
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// ================================================================================================
#ifndef GENERICIMAGEDATALOCAL_H__
#define GENERICIMAGEDATALOCAL_H__
#include "tgt/vector.h"
#include "core/datastructures/imagedatalocal.h"
#include "core/tools/typetraits.h"
#include <cstring> // needed for memcpy
namespace campvis {
/**
* Templated version of ImageDataLocal, storing image data in the local memory.
*
* \sa TypeTraits
* \tparam BASETYPE Base type of the image data (type of a single channel of an image element)
* \tparam NUMCHANNELS Number of channels of the image data.
*/
template<typename BASETYPE, size_t NUMCHANNELS>
class GenericImageDataLocal : public ImageDataLocal {
public:
/// Type of one single image element
typedef typename TypeTraits<BASETYPE, NUMCHANNELS>::ElementType ElementType;
/// Type of this template instantiation
typedef GenericImageDataLocal<BASETYPE, NUMCHANNELS> ThisType;
/**
* Creates a new strongly typed ImageData object storing the image in the local memory.
*
* \param dimensionality Dimensionality of data
* \param size Size of this image (number of elements per dimension)
* \param data Pointer to the image data, must not be 0, GenericImageDataLocal takes ownership of this pointer!
*/
GenericImageDataLocal(size_t dimensionality, const tgt::svec3& size, ElementType* data);
/**
* Destructor
*/
virtual ~GenericImageDataLocal();
/// \see AbstractData::clone()
virtual ThisType* clone() const;
/// \see AbstractData::getLocalMemoryFootprint()
virtual size_t getLocalMemoryFootprint() const;
/// \see AbstractData::getVideoMemoryFootprint()
virtual size_t getVideoMemoryFootprint() const;
/// \see ImageData::getSubImage
virtual ThisType* getSubImage(const tgt::svec3& llf, const tgt::svec3& urb) const;
/**
* Returns a WeaklyTypedPointer to the image data.
* \note The pointer is still owned by this ImageDataLocal. If you want a copy, use clone().
* \return A WeaklyTypedPointer to the image data.
*/
virtual const WeaklyTypedPointer getWeaklyTypedPointer() const;
/// \see ImageDataLocal::getElementNormalized
virtual float getElementNormalized(size_t index, size_t channel) const;
/// \see ImageDataLocal::getElementNormalized
virtual float getElementNormalized(const tgt::svec3& position, size_t channel) const;
/// \see ImageDataLocal::getElementNormalizedLinear
virtual float getElementNormalizedLinear(const tgt::vec3& position, size_t channel) const;
/// \see ImageDataLocal::setElementNormalized
virtual void setElementNormalized(size_t index, size_t channel, float value);
/// \see ImageDataLocal::setElementNormalized
virtual void setElementNormalized(const tgt::svec3& position, size_t channel, float value);
/**
* Returns the image element at the given index \a index.
* \param index Array index of the image element to return.
* \return Image element at index \a index.
*/
ElementType& getElement(size_t index);
/**
* Returns the image element at the given position in image space.
* \param position Pixel/voxel coordinates of the image element to return.
* \return Image element at the coordinates \a position.
*/
ElementType& getElement(const tgt::svec3& position);
/**
* Returns the image element at the given index \a index.
* \param index Array index of the image element to return.
* \return Image element at index \a index.
*/
const ElementType& getElement(size_t index) const;
/**
* Returns the image element at the given position in image space.
* \param position Pixel/voxel coordinates of the image element to return.
* \return Image element at the coordinates \a position.
*/
const ElementType& getElement(const tgt::svec3& position) const;
/**
* Sets the image element at the given index to the value \a value.
* \param index Array index of the image element to change.
* \param value New value of the specified image element.
*/
void setElement(size_t index, const ElementType& value);
/**
* Sets the image element at the given position in image space to the value \a value.
* \param position Pixel/voxel coordinates of the image element to change.
* \param value New value of the specified image element.
*/
void setElement(const tgt::svec3& position, const ElementType& value);
/**
* Returns a pointer to the image data.
* \return _data
*/
ElementType* getImageData();
/**
* Returns a const pointer to the image data.
* \return _data
*/
const ElementType* getImageData() const;
/**
* Returns the image element at the given coordinates \a position using bi-/trilinear filtering.
* \param position Pixel/voxel coordinates of the image element to return.
* \return Bi-/Trilinear filtered image element at the specified coordinates.
*/
ElementType getElementLinear(const tgt::vec3 position) const;
protected:
ElementType* _data;
};
// = Template implementation ======================================================================
template<typename BASETYPE, size_t NUMCHANNELS>
campvis::GenericImageDataLocal<BASETYPE, NUMCHANNELS>::GenericImageDataLocal(size_t dimensionality, const tgt::svec3& size, ElementType* data)
: ImageDataLocal(dimensionality, size, TypeTraits<BASETYPE, NUMCHANNELS>::weaklyTypedPointerBaseType, NUMCHANNELS)
, _data(data)
{
if (_data == 0) {
size_t numElements = tgt::hmul(_size);
_data = new ElementType[numElements];
memset(_data, 0, numElements * TypeTraits<BASETYPE, NUMCHANNELS>::elementSize);
}
}
template<typename BASETYPE, size_t NUMCHANNELS>
campvis::GenericImageDataLocal<BASETYPE, NUMCHANNELS>::~GenericImageDataLocal() {
delete _data;
}
template<typename BASETYPE, size_t NUMCHANNELS>
GenericImageDataLocal<BASETYPE, NUMCHANNELS>* campvis::GenericImageDataLocal<BASETYPE, NUMCHANNELS>::clone() const {
size_t numElements = tgt::hmul(_size);
ElementType* newData = new ElementType[numElements];
memcpy(newData, _data, numElements * sizeof(ElementType));
return new ThisType(_dimensionality, _size, newData);
}
template<typename BASETYPE, size_t NUMCHANNELS>
size_t campvis::GenericImageDataLocal<BASETYPE, NUMCHANNELS>::getLocalMemoryFootprint() const {