web-dev-qa-db-fra.com

Échelle de gris à rouge-vert-bleu (MATLAB Jet)

On m'a donné un ensemble de données qui est essentiellement une image, mais chaque pixel de l'image est représenté comme une valeur de -1 à 1 inclus. J'écris une application qui doit prendre ces valeurs de niveaux de gris -1 à 1 et les mapper à la valeur RVB associée pour l'échelle de couleur MATLAB "Jet" (dégradé de couleur rouge-vert-bleu).

Je suis curieux de savoir si quelqu'un sait comment prendre une valeur linéaire (comme -1 à 1) et la mapper à cette échelle. Notez que je n'utilise pas MATLAB pour cela (je ne peux pas non plus), j'ai juste besoin de prendre la valeur en niveaux de gris et de la mettre sur le dégradé Jet.

Merci, Adam

34
Adam Shook

J'espère que c'est ce que vous recherchez:

double interpolate( double val, double y0, double x0, double y1, double x1 ) {
  return (val-x0)*(y1-y0)/(x1-x0) + y0;
}
double blue( double grayscale ) {
  if ( grayscale < -0.33 ) return 1.0;
  else if ( grayscale < 0.33 ) return interpolate( grayscale, 1.0, -0.33, 0.0, 0.33 );
  else return 0.0;
}
double green( double grayscale ) {
  if ( grayscale < -1.0 ) return 0.0; // unexpected grayscale value
  if  ( grayscale < -0.33 ) return interpolate( grayscale, 0.0, -1.0, 1.0, -0.33 );
  else if ( grayscale < 0.33 ) return 1.0;
  else if ( grayscale <= 1.0 ) return interpolate( grayscale, 1.0, 0.33, 0.0, 1.0 );
  else return 1.0; // unexpected grayscale value
}
double red( double grayscale ) {
  if ( grayscale < -0.33 ) return 0.0;
  else if ( grayscale < 0.33 ) return interpolate( grayscale, 0.0, -0.33, 1.0, 0.33 );
  else return 1.0;
}

Je ne sais pas si cette échelle est 100% identique à l'image que vous avez liée, mais elle devrait être très similaire.

[~ # ~] mise à jour [~ # ~] J'ai réécrit le code selon la description de la palette Jet de MatLab trouvée ici

double interpolate( double val, double y0, double x0, double y1, double x1 ) {
    return (val-x0)*(y1-y0)/(x1-x0) + y0;
}

double base( double val ) {
    if ( val <= -0.75 ) return 0;
    else if ( val <= -0.25 ) return interpolate( val, 0.0, -0.75, 1.0, -0.25 );
    else if ( val <= 0.25 ) return 1.0;
    else if ( val <= 0.75 ) return interpolate( val, 1.0, 0.25, 0.0, 0.75 );
    else return 0.0;
}

double red( double gray ) {
    return base( gray - 0.5 );
}
double green( double gray ) {
    return base( gray );
}
double blue( double gray ) {
    return base( gray + 0.5 );
}
22
Ilya Denisov

Considérez la fonction suivante (écrite par Paul Bourke - recherchez Colour Ramping for Data Visualisation):

/*
   Return a RGB colour value given a scalar v in the range [vmin,vmax]
   In this case each colour component ranges from 0 (no contribution) to
   1 (fully saturated), modifications for other ranges is trivial.
   The colour is clipped at the end of the scales if v is outside
   the range [vmin,vmax]
*/

typedef struct {
    double r,g,b;
} COLOUR;

COLOUR GetColour(double v,double vmin,double vmax)
{
   COLOUR c = {1.0,1.0,1.0}; // white
   double dv;

   if (v < vmin)
      v = vmin;
   if (v > vmax)
      v = vmax;
   dv = vmax - vmin;

   if (v < (vmin + 0.25 * dv)) {
      c.r = 0;
      c.g = 4 * (v - vmin) / dv;
   } else if (v < (vmin + 0.5 * dv)) {
      c.r = 0;
      c.b = 1 + 4 * (vmin + 0.25 * dv - v) / dv;
   } else if (v < (vmin + 0.75 * dv)) {
      c.r = 4 * (v - vmin - 0.5 * dv) / dv;
      c.b = 0;
   } else {
      c.g = 1 + 4 * (vmin + 0.75 * dv - v) / dv;
      c.b = 0;
   }

   return(c);
}

Dans votre cas, vous l'utiliseriez pour mapper des valeurs dans la plage [-1,1] en couleurs comme (il est simple de le traduire du code C en fonction MATLAB):

c = GetColour(v,-1.0,1.0);

Cela produit la rampe de couleur "chaud-froid" suivante:

color_ramp

Il représente essentiellement une promenade sur les bords du cube de couleur RVB du bleu au rouge (en passant par le cyan, le vert, le jaune) et en interpolant les valeurs le long de ce chemin.

color_cube


Notez que ceci est légèrement différent de la palette de couleurs "Jet" utilisée dans MATLAB, qui pour autant que je sache, passe par le chemin suivant:

#00007F: dark blue
#0000FF: blue
#007FFF: Azure
#00FFFF: cyan
#7FFF7F: light green
#FFFF00: yellow
#FF7F00: orange
#FF0000: red
#7F0000: dark red

Voici une comparaison que j'ai faite dans MATLAB:

%# values
num = 64;
v = linspace(-1,1,num);

%# colormaps
clr1 = jet(num);
clr2 = zeros(num,3);
for i=1:num
    clr2(i,:) = GetColour(v(i), v(1), v(end));
end

Ensuite, nous traçons les deux en utilisant:

figure
subplot(4,1,1), imagesc(v), colormap(clr), axis off
subplot(4,1,2:4), h = plot(v,clr); axis tight
set(h, {'Color'},{'r';'g';'b'}, 'LineWidth',3)

jethot_to_cold

Vous pouvez maintenant modifier le code C ci-dessus et utiliser les points d'arrêt suggérés pour obtenir quelque chose de similaire à la carte de couleurs jet (ils utilisent tous une interpolation linéaire sur les canaux R, G, B comme vous pouvez le voir sur les graphiques ci-dessus) ...

72
Amro

Les autres réponses traitent l'interpolation comme une fonction linéaire par morceaux. Cela peut être simplifié en utilisant une fonction de base triangulaire serrée pour l'interpolation. Nous avons besoin d'une fonction de blocage qui mappe son entrée à l'intervalle unitaire fermé:

clamp(x)=max(0, min(x, 1))

Et une fonction de base pour l'interpolation:

N(t) = clamp(1.5 - |2t|)

La couleur devient alors:

r = N(t - 0.5), g = N(t), b = N(t + 0.5)

Le tracé de -1 à 1 donne:

Plot of RGB values from -1 to 1

Ce qui est le même que celui fourni dans cette réponse . Utiliser une implémentation efficace de la pince :

double clamp(double v)
{
  const double t = v < 0 ? 0 : v;
  return t > 1.0 ? 1.0 : t;
}

et en vous assurant que votre valeur t est dans [-1, 1], alors la couleur du jet est simplement:

double red   = clamp(1.5 - std::abs(2.0 * t - 1.0));
double green = clamp(1.5 - std::abs(2.0 * t));
double blue  = clamp(1.5 - std::abs(2.0 * t + 1.0));

Comme le montre le lien ci-dessus sur l'implémentation de clamp, le compilateur peut optimiser les branches. Le compilateur peut également utiliser des intrinsèques pour définir le bit de signe pour std::abs éliminer une autre branche.

"Chaud à froid"

Un traitement similaire peut être utilisé pour la cartographie des couleurs "chaud-froid". Dans ce cas, les fonctions de base et de couleur sont:

N(t) = clamp(2 - |2t|)

r(t)=N(t-1), g(t) = N(t), b(t) = N(t+1)

Et le tracé chaud-froid pour [-1, 1]:

Hot-to-cold plot

Programme OpenGL Shader

L'élimination des branches explicites rend cette approche efficace pour l'implémentation en tant que programme de shader OpenGL. GLSL fournit des fonctions intégrées pour abs et clamp qui fonctionnent sur des vecteurs 3D. La vectorisation du calcul des couleurs et le fait de préférer les fonctions intégrées à la ramification peuvent fournir des gains de performances significatifs. Vous trouverez ci-dessous une implémentation dans GLSL qui renvoie la couleur du jet RVB sous la forme d'un vec3. Notez que la fonction de base a été modifiée de telle sorte que t doit se trouver dans [0,1] plutôt que dans la plage utilisée dans les autres exemples.

vec3 jet(float t)
{
  return clamp(vec3(1.5) - abs(4.0 * vec3(t) + vec3(-3, -2, -1)), vec3(0), vec3(1));
}
8
Joshua Fraser

Je ne sais pas vraiment pourquoi il y a tant de réponses complexes à cette simple équation. Sur la base du tableau de cartes en couleurs MatLab JET Hot-to-Cold et du graphique présentés ci-dessus dans le commentaire d'Amro (merci), la logique est très simple pour calculer les valeurs RVB à l'aide de mathématiques rapides/basiques.

J'utilise la fonction suivante pour le rendu en direct de données normalisées pour afficher des spectrogrammes et c'est incroyablement rapide et efficace sans mathématiques complexes en dehors de la multiplication et de la division en double précision, simplifiées par le chaînage logique ternaire. Ce code est C # mais très facilement porté vers presque n'importe quel autre langage (désolé PHP, vous n'avez pas de chance grâce à un ordre de chaîne ternaire anormal).

public byte[] GetMatlabRgb(double ordinal)
{
    byte[] triplet = new byte[3];
    triplet[0] = (ordinal < 0.0)  ? (byte)0 : (ordinal >= 0.5)  ? (byte)255 : (byte)(ordinal / 0.5 * 255);
    triplet[1] = (ordinal < -0.5) ? (byte)((ordinal + 1) / 0.5 * 255) : (ordinal > 0.5) ? (byte)(255 - ((ordinal - 0.5) / 0.5 * 255)) : (byte)255;
    triplet[2] = (ordinal > 0.0)  ? (byte)0 : (ordinal <= -0.5) ? (byte)255 : (byte)(ordinal * -1.0 / 0.5 * 255);
    return triplet;
}

La fonction prend une plage ordinale de -1,0 à 1,0 selon la spécification de couleur JET, bien que cette fonction ne vérifie pas si vous êtes en dehors de cette plage (je le fais avant mon appel ici).

Assurez-vous donc de vérifier la validité/les limites avant d'appeler cette fonction ou ajoutez simplement votre propre limitation pour limiter la valeur lorsque vous l'implémentez vous-même.

Cette mise en œuvre ne prend pas en compte la luminosité, elle ne peut donc pas être considérée comme une mise en œuvre puriste, mais vous permet de vous asseoir assez bien et est beaucoup plus rapide.

3
tpartee

On dirait que vous avez des valeurs de teinte d'un système HSL et que la saturation et la luminosité sont implicites. Recherchez la conversion HSL en RVB sur Internet et vous trouverez de nombreuses explications, du code, etc. (Voici n lien )

Dans votre cas particulier, supposons cependant que toutes les saturations de couleurs soient définies par défaut sur 1 et la luminosité sur 0,5. Voici la formule que vous pouvez utiliser pour obtenir les valeurs RVB:

Imaginez pour chaque pixel, vous avez h la valeur que vous lisez à partir de vos données.

hue = (h+1.0)/2;  // This is to make it in range [0, 1]
temp[3] = {hue+1.0/3, hue, hue-1.0/3};
if (temp[0] > 1.0)
    temp[0] -= 1.0;
if (temp[2] < 0.0)
    temp[2] += 1.0;

float RGB[3];
for (int i = 0; i < 3; ++i)
{
    if (temp[i]*6.0 < 1.0)
        RGB[i] = 6.0f*temp[i];
    else if (temp[i]*2.0 < 1.0)
        RGB[i] = 1;
    else if (temp[i]*3.0 < 2.0)
        RGB[i] = ((2.0/3.0)-temp[i])*6.0f;
    else
        RGB[i] = 0;
}

Et là, vous avez les valeurs RVB dans RGB toutes dans la plage [0, 1]. Notez que la conversion d'origine est plus complexe, je l'ai simplifiée en fonction des valeurs de saturation = 1 et luminosité = 0,5

Pourquoi cette formule? Voir ceci entrée wikipedia

1
Shahbaz

Ce n'est probablement pas exactement la même chose, mais cela peut être assez proche de vos besoins:

if (-0.75 > value) {
    blue = 1.75 + value;
} else if (0.25 > value) {
    blue = 0.25 - value;
} else {
    blue = 0;
}

if ( -0.5 > value) {
    green = 0;
} else if (0.5 > value) {
    green = 1 - 2*abs(value);
} else {
    green = 0;
}

if ( -0.25 > value) {
    red = 0;
} else if (0.75 > value) {
    red = 0.25 + value;
} else {
    red = 1.75 - value;
}
0
IronMensan