diff layer/Colour3DPlotLayer.cpp @ 197:6b023411087b

* Work on harmonising colour and scale ranges between types of layer * Add normalize options to colour 3d plot layer
author Chris Cannam
date Thu, 01 Feb 2007 14:31:28 +0000
parents 57c2350a8c40
children c2ed5014d4ff
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp	Wed Jan 31 12:13:47 2007 +0000
+++ b/layer/Colour3DPlotLayer.cpp	Thu Feb 01 14:31:28 2007 +0000
@@ -17,6 +17,8 @@
 
 #include "view/View.h"
 #include "base/Profiler.h"
+#include "base/LogRange.h"
+#include "ColourMapper.h"
 
 #include <QPainter>
 #include <QImage>
@@ -32,7 +34,11 @@
 Colour3DPlotLayer::Colour3DPlotLayer() :
     m_model(0),
     m_cache(0),
-    m_colourScale(LinearScale)
+    m_cacheStart(0),
+    m_colourScale(LinearScale),
+    m_colourMap(0),
+    m_normalizeColumns(false),
+    m_normalizeVisibleArea(false)
 {
     
 }
@@ -81,26 +87,37 @@
 Colour3DPlotLayer::getProperties() const
 {
     PropertyList list;
+    list.push_back("Colour");
     list.push_back("Colour Scale");
+    list.push_back("Normalize Columns");
+    list.push_back("Normalize Visible Area");
     return list;
 }
 
 QString
 Colour3DPlotLayer::getPropertyLabel(const PropertyName &name) const
 {
-    if (name == "Colour Scale") return tr("Colour Scale");
+    if (name == "Colour") return tr("Colour");
+    if (name == "Colour Scale") return tr("Scale");
+    if (name == "Normalize Columns") return tr("Normalize Columns");
+    if (name == "Normalize Visible Area") return tr("Normalize Visible Area");
     return "";
 }
 
 Layer::PropertyType
 Colour3DPlotLayer::getPropertyType(const PropertyName &name) const
 {
+    if (name == "Normalize Columns") return ToggleProperty;
+    if (name == "Normalize Visible Area") return ToggleProperty;
     return ValueProperty;
 }
 
 QString
 Colour3DPlotLayer::getPropertyGroupName(const PropertyName &name) const
 {
+    if (name == "Normalize Columns" ||
+        name == "Normalize Visible Area" ||
+	name == "Colour Scale") return tr("Scale");
     return QString();
 }
 
@@ -117,10 +134,25 @@
     if (name == "Colour Scale") {
 
 	*min = 0;
-	*max = 3;
+	*max = 2;
 
 	deft = (int)m_colourScale;
 
+    } else if (name == "Colour") {
+
+	*min = 0;
+	*max = ColourMapper::getColourMapCount() - 1;
+
+	deft = m_colourMap;
+
+    } else if (name == "Normalize Columns") {
+	
+	deft = (m_normalizeColumns ? 1 : 0);
+
+    } else if (name == "Normalize Visible Area") {
+	
+	deft = (m_normalizeVisibleArea ? 1 : 0);
+
     } else {
 	deft = Layer::getPropertyRangeAndValue(name, min, max);
     }
@@ -132,13 +164,15 @@
 Colour3DPlotLayer::getPropertyValueLabel(const PropertyName &name,
 				    int value) const
 {
+    if (name == "Colour") {
+        return ColourMapper::getColourMapName(value);
+    }
     if (name == "Colour Scale") {
 	switch (value) {
 	default:
-	case 0: return tr("Linear");
-	case 1: return tr("Absolute");
-	case 2: return tr("Meter");
-	case 3: return tr("dB");
+	case 0: return tr("Linear Scale");
+	case 1: return tr("Log Scale");
+	case 2: return tr("+/-1 Scale");
 	}
     }
     return tr("<unknown>");
@@ -151,10 +185,15 @@
 	switch (value) {
 	default:
 	case 0: setColourScale(LinearScale); break;
-	case 1: setColourScale(AbsoluteScale); break;
-	case 2: setColourScale(MeterScale); break;
-	case 3: setColourScale(dBScale); break;
+	case 1: setColourScale(LogScale); break;
+	case 2: setColourScale(PlusMinusOneScale); break;
 	}
+    } else if (name == "Colour") {
+        setColourMap(value);
+    } else if (name == "Normalize Columns") {
+	setNormalizeColumns(value ? true : false);
+    } else if (name == "Normalize Visible Area") {
+	setNormalizeVisibleArea(value ? true : false);
     }
 }
 
@@ -167,9 +206,49 @@
     emit layerParametersChanged();
 }
 
+void
+Colour3DPlotLayer::setColourMap(int map)
+{
+    if (m_colourMap == map) return;
+    m_colourMap = map;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+void
+Colour3DPlotLayer::setNormalizeColumns(bool n)
+{
+    if (m_normalizeColumns == n) return;
+    m_normalizeColumns = n;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+bool
+Colour3DPlotLayer::getNormalizeColumns() const
+{
+    return m_normalizeColumns;
+}
+
+void
+Colour3DPlotLayer::setNormalizeVisibleArea(bool n)
+{
+    if (m_normalizeVisibleArea == n) return;
+    m_normalizeVisibleArea = n;
+    cacheInvalid();
+    emit layerParametersChanged();
+}
+
+bool
+Colour3DPlotLayer::getNormalizeVisibleArea() const
+{
+    return m_normalizeVisibleArea;
+}
+
 bool
 Colour3DPlotLayer::isLayerScrollable(const View *v) const
 {
+    if (m_normalizeVisibleArea) return false;
     QPoint discard;
     return !v->shouldIlluminateLocalFeatures(this, discard);
 }
@@ -295,6 +374,155 @@
 }
 
 void
+Colour3DPlotLayer::getColumn(size_t col,
+                             DenseThreeDimensionalModel::Column &values) const
+{
+    m_model->getColumn(col, values);
+
+    float colMax = 0.f;
+
+    float min = 0.f, max = 0.f;
+    if (m_normalizeColumns) {
+        min = m_model->getMinimumLevel();
+        max = m_model->getMaximumLevel();
+    }
+
+    if (m_normalizeColumns) {
+        for (size_t y = 0; y < values.size(); ++y) {
+            if (values[y] > colMax || y == 0) colMax = values[y];
+        }
+        if (m_colourScale == LogScale) {
+            colMax = LogRange::map(colMax);
+        }
+    }
+    
+    for (size_t y = 0; y < values.size(); ++y) {
+        
+        float value = min;
+
+        value = values[y];
+        if (m_colourScale == LogScale) {
+            value = LogRange::map(value);
+        }
+
+        if (m_normalizeColumns) {
+            if (colMax != 0) {
+                value = max * (value / colMax);
+            } else {
+                value = 0;
+            }
+        }
+
+        values[y] = value;
+    }    
+}
+    
+void
+Colour3DPlotLayer::fillCache(size_t firstBin, size_t lastBin) const
+{
+    size_t modelStart = m_model->getStartFrame();
+    size_t modelEnd = m_model->getEndFrame();
+    size_t modelResolution = m_model->getResolution();
+
+    std::cerr << "Colour3DPlotLayer::fillCache: " << firstBin << " -> " << lastBin << std::endl;
+
+    if (!m_normalizeVisibleArea || m_normalizeColumns) {
+        firstBin = modelStart / modelResolution;
+        lastBin = modelEnd / modelResolution;
+    }
+
+    size_t cacheWidth = lastBin - firstBin + 1;
+    size_t cacheHeight = m_model->getHeight();
+
+    if (m_cache &&
+	(m_cacheStart != firstBin ||
+         m_cache->width() != cacheWidth ||
+	 m_cache->height() != cacheHeight)) {
+
+	delete m_cache;
+	m_cache = 0;
+    }
+
+    if (m_cache) return;
+
+    m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8);
+    m_cacheStart = firstBin;
+
+    std::cerr << "Cache size " << cacheWidth << "x" << cacheHeight << " starting " << m_cacheStart << std::endl;
+
+    m_cache->setNumColors(256);
+    DenseThreeDimensionalModel::Column values;
+
+    float min = m_model->getMinimumLevel();
+    float max = m_model->getMaximumLevel();
+
+    if (m_colourScale == LogScale) {
+        LogRange::mapRange(min, max);
+    } else if (m_colourScale == PlusMinusOneScale) {
+        min = -1.f;
+        max = 1.f;
+    }
+    
+    if (max == min) max = min + 1.0;
+    
+    ColourMapper mapper(m_colourMap, 0.f, 255.f);
+    
+    for (int index = 0; index < 256; ++index) {
+        
+        QColor colour = mapper.map(index);
+        m_cache->setColor(index, qRgb(colour.red(), colour.green(), colour.blue()));
+    }
+    
+    m_cache->fill(0);
+
+    float visibleMax = 0.f;
+
+    if (m_normalizeVisibleArea && !m_normalizeColumns) {
+        
+        for (size_t c = firstBin; c <= lastBin; ++c) {
+	
+            values.clear();
+            getColumn(c, values);
+
+            float colMax = 0.f;
+
+            for (size_t y = 0; y < m_model->getHeight(); ++y) {
+                if (y >= values.size()) break;
+                if (y == 0 || values[y] > colMax) colMax = values[y];
+            }
+
+            if (c == firstBin || colMax > visibleMax) visibleMax = colMax;
+        }
+    }
+    
+    for (size_t c = firstBin; c <= lastBin; ++c) {
+	
+        values.clear();
+        getColumn(c, values);
+
+        for (size_t y = 0; y < m_model->getHeight(); ++y) {
+
+            float value = min;
+            if (y < values.size()) {
+                value = values[y];
+            }
+            
+            if (m_normalizeVisibleArea && !m_normalizeColumns) {
+                if (visibleMax != 0) {
+                    value = max * (value / visibleMax);
+                }
+            }
+
+            int pixel = int(((value - min) * 256) / (max - min));
+            if (pixel < 0) pixel = 0;
+            if (pixel > 255) pixel = 255;
+
+            m_cache->setPixel(c - firstBin, y, pixel);
+        }
+    }
+}
+
+void
 Colour3DPlotLayer::paint(View *v, QPainter &paint, QRect rect) const
 {
 //    Profiler profiler("Colour3DPlotLayer::paint");
@@ -311,96 +539,39 @@
 	return;
     }
 
+    if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->rect();
+
     size_t modelStart = m_model->getStartFrame();
     size_t modelEnd = m_model->getEndFrame();
     size_t modelResolution = m_model->getResolution();
 
-    size_t cacheWidth = (modelEnd - modelStart) / modelResolution + 1;
-    size_t cacheHeight = m_model->getHeight();
+    // The cache is from the model's start frame to the model's end
+    // frame at the model's window increment frames per pixel.  We
+    // want to draw from our start frame + x0 * zoomLevel to our start
+    // frame + x1 * zoomLevel at zoomLevel frames per pixel.
 
-    if (m_cache &&
-	(m_cache->width() != cacheWidth ||
-	 m_cache->height() != cacheHeight)) {
+    //  We have quite different paint mechanisms for rendering "large"
+    //  bins (more than one bin per pixel in both directions) and
+    //  "small".  This is "large"; see paintDense below for "small".
 
-	delete m_cache;
-	m_cache = 0;
-    }
+    int x0 = rect.left();
+    int x1 = rect.right() + 1;
 
-    if (!m_cache) { 
+    int h = v->height();
 
-	m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8);
+    float srRatio =
+        float(v->getViewManager()->getMainModelSampleRate()) /
+        float(m_model->getSampleRate());
 
-        std::cerr << "Cache size " << cacheWidth << "x" << cacheHeight << std::endl;
+    int sx0 = int((v->getFrameForX(x0) / srRatio - long(modelStart))
+                  / long(modelResolution));
+    int sx1 = int((v->getFrameForX(x1) / srRatio - long(modelStart))
+                  / long(modelResolution));
+    int sh = m_model->getHeight();
 
-	m_cache->setNumColors(256);
-	DenseThreeDimensionalModel::Column values;
-
-	float min = m_model->getMinimumLevel();
-	float max = m_model->getMaximumLevel();
-
-	if (max == min) max = min + 1.0;
-
-        int zeroIndex = 0;
-        if (min < 0.f) {
-            if (m_colourScale == LinearScale) {
-                zeroIndex = int(((-min) * 256) / (max - min));
-            } else {
-                max = std::max(-min, max);
-                min = 0;
-            }
-        }
-        if (zeroIndex < 0) zeroIndex = 0;
-        if (zeroIndex > 255) zeroIndex = 255;
-
-        //!!! want this and spectrogram to share a colour mapping unit
-
-	for (int index = 0; index < 256; ++index) {
-            int effective = abs(((index - zeroIndex) * 255) /
-                                std::max(255 - zeroIndex, zeroIndex));
-	    int hue = 256 - effective;
-            if (zeroIndex > 0) {
-                if (index <= zeroIndex) hue = 255;
-                else hue = 0;
-            }
-            while (hue < 0) hue += 255;
-            while (hue > 255) hue -= 255;
-            int saturation = effective / 2 + 128;
-            if (saturation < 0) saturation = -saturation;
-            if (saturation > 255) saturation = 255;
-            int value = effective;
-            if (value < 0) value = -value;
-            if (value > 255) value = 255;
-//            std::cerr << "min: " << min << ", max: " << max << ", zi " << zeroIndex << ", index " << index << ": " << hue << ", " << saturation << ", " << value << std::endl;
-	    QColor colour = QColor::fromHsv(hue, saturation, value);
-//            std::cerr << "rgb: " << colour.red() << "," << colour.green() << "," << colour.blue() << std::endl;
-	    m_cache->setColor(index, qRgb(colour.red(), colour.green(), colour.blue()));
-	}
-
-	m_cache->fill(zeroIndex);
-
-	for (size_t f = modelStart; f <= modelEnd; f += modelResolution) {
-	
-	    values.clear();
-	    m_model->getColumn(f / modelResolution, values);
-	    
-	    for (size_t y = 0; y < m_model->getHeight(); ++y) {
-
-		float value = min;
-		if (y < values.size()) {
-                    value = values[y];
-                    if (m_colourScale != LinearScale) {
-                        value = fabs(value);
-                    }
-                }
-
-		int pixel = int(((value - min) * 256) / (max - min));
-                if (pixel < 0) pixel = 0;
-		if (pixel > 255) pixel = 255;
-
-		m_cache->setPixel(f / modelResolution, y, pixel);
-	    }
-	}
-    }
+    if (sx0 > 0) --sx0;
+    fillCache(sx0 < 0 ? 0 : sx0,
+              sx1 < 0 ? 0 : sx1);
 
     if (m_model->getHeight() >= v->height() ||
         modelResolution < v->getZoomLevel() / 2) {
@@ -408,29 +579,6 @@
         return;
     }
 
-    int x0 = rect.left();
-    int x1 = rect.right() + 1;
-
-    int h = v->height();
-
-    // The cache is from the model's start frame to the model's end
-    // frame at the model's window increment frames per pixel.  We
-    // want to draw from our start frame + x0 * zoomLevel to our start
-    // frame + x1 * zoomLevel at zoomLevel frames per pixel.
-
-    //!!! Strictly speaking we want quite different paint mechanisms
-    //for models that have more than one bin per pixel in either
-    //direction.  This one is only really appropriate for models with
-    //far fewer bins in both directions.
-
-    float srRatio =
-        float(v->getViewManager()->getMainModelSampleRate()) /
-        float(m_model->getSampleRate());
-
-    int sx0 = int((v->getFrameForX(x0) / srRatio - long(modelStart)) / long(modelResolution));
-    int sx1 = int((v->getFrameForX(x1) / srRatio - long(modelStart)) / long(modelResolution));
-    int sh = m_model->getHeight();
-
 #ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT
     std::cerr << "Colour3DPlotLayer::paint: w " << w << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sw << ", sh " << sh << std::endl;
     std::cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", resolution " << m_model->getResolution() << std::endl;
@@ -440,8 +588,11 @@
     bool illuminate = v->shouldIlluminateLocalFeatures(this, illuminatePos);
     char labelbuf[10];
 
-    for (int sx = sx0 - 1; sx <= sx1; ++sx) {
+    for (int sx = sx0; sx <= sx1; ++sx) {
 
+        int scx = 0;
+        if (sx > m_cacheStart) scx = sx - m_cacheStart;
+        
 	int fx = sx * int(modelResolution);
 
 	if (fx + modelResolution < int(modelStart) ||
@@ -461,9 +612,9 @@
 
 	    int ry0 = h - (sy * h) / sh - 1;
 	    QRgb pixel = qRgb(255, 255, 255);
-	    if (sx >= 0 && sx < m_cache->width() &&
+	    if (scx >= 0 && scx < m_cache->width() &&
 		sy >= 0 && sy < m_cache->height()) {
-		pixel = m_cache->pixel(sx, sy);
+		pixel = m_cache->pixel(scx, sy);
 	    }
 
 	    QRect r(rx0, ry0 - h / sh - 1, rw, h / sh + 1);
@@ -499,9 +650,9 @@
 	    paint.drawRect(r);
 
 	    if (showLabel) {
-		if (sx >= 0 && sx < m_cache->width() &&
+		if (scx >= 0 && scx < m_cache->width() &&
 		    sy >= 0 && sy < m_cache->height()) {
-		    float value = m_model->getValueAt(sx, sy);
+		    float value = m_model->getValueAt(scx, sy);
 		    sprintf(labelbuf, "%06f", value);
 		    QString text(labelbuf);
 		    paint.setPen(Qt::white);
@@ -566,7 +717,10 @@
 
             for (int sx = sx0i; sx <= sx1i; ++sx) {
 
-                if (sx < 0 || sx >= m_cache->width()) continue;
+                int scx = 0;
+                if (sx > m_cacheStart) scx = sx - m_cacheStart;
+        
+                if (scx < 0 || scx >= m_cache->width()) continue;
 
                 for (int sy = sy0i; sy <= sy1i; ++sy) {
 
@@ -578,8 +732,8 @@
                     if (sy == sy0i) prop *= (sy + 1) - sy0;
                     if (sy == sy1i) prop *= sy1 - sy;
 
-                    mag += prop * m_cache->pixelIndex(sx, sy);
-                    max = std::max(max, m_cache->pixelIndex(sx, sy));
+                    mag += prop * m_cache->pixelIndex(scx, sy);
+                    max = std::max(max, m_cache->pixelIndex(scx, sy));
                     div += prop;
                 }
             }
@@ -624,6 +778,43 @@
     return true;
 }
 
+QString
+Colour3DPlotLayer::toXmlString(QString indent, QString extraAttributes) const
+{
+    QString s;
+    
+    s += QString("scale=\"%1\" "
+                 "colourScheme=\"%2\" "
+		 "normalizeColumns=\"%3\" "
+                 "normalizeVisibleArea=\"%4\"")
+	.arg((int)m_colourScale)
+        .arg(m_colourMap)
+        .arg(m_normalizeColumns ? "true" : "false")
+        .arg(m_normalizeVisibleArea ? "true" : "false");
+
+    return Layer::toXmlString(indent, extraAttributes + " " + s);
+}
+
+void
+Colour3DPlotLayer::setProperties(const QXmlAttributes &attributes)
+{
+    bool ok = false;
+
+    ColourScale scale = (ColourScale)attributes.value("scale").toInt(&ok);
+    if (ok) setColourScale(scale);
+
+    int colourMap = attributes.value("colourScheme").toInt(&ok);
+    if (ok) setColourMap(colourMap);
+
+    bool normalizeColumns =
+        (attributes.value("normalizeColumns").trimmed() == "true");
+    setNormalizeColumns(normalizeColumns);
+
+    bool normalizeVisibleArea =
+        (attributes.value("normalizeVisibleArea").trimmed() == "true");
+    setNormalizeVisibleArea(normalizeVisibleArea);
+}
+
 #ifdef INCLUDE_MOCFILES
 #include "Colour3DPlotLayer.moc.cpp"
 #endif