Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://doi.org/10.5201/ipol.2018.236
07 May 2020, 17:33:00 UTC
  • Code
  • Branches (1)
  • Releases (0)
  • Visits
    • Branches
    • Releases
    • HEAD
    • cd8faa35b66794fecfc5c1e49c826d1a287ab410
    No releases to show
  • a104235
  • /
  • mlheIPOL
  • /
  • library
  • /
  • libmlhe.cpp
Raw File Download

To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
Select below a type of object currently browsed in order to display its associated SWHID and permalink.

  • content
  • directory
  • revision
  • snapshot
origin badgecontent badge
swh:1:cnt:f07023812edaeebc891f9b1eafc11bf25e4a3d42
origin badgedirectory badge
swh:1:dir:83eec49ac2f763acb05ac926c7cca0083a5f75f8
origin badgerevision badge
swh:1:rev:cd8faa35b66794fecfc5c1e49c826d1a287ab410
origin badgesnapshot badge
swh:1:snp:994f6ca7c49b1012768c4a5a6470f17f28d0e294

This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
Select below a type of object currently browsed in order to generate citations for them.

  • content
  • directory
  • revision
  • snapshot
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Generate software citation in BibTex format (requires biblatex-software package)
Generating citation ...
Tip revision: cd8faa35b66794fecfc5c1e49c826d1a287ab410 authored by Software Heritage on 14 November 2018, 00:00:00 UTC
ipol: Deposit 602 in collection ipol
Tip revision: cd8faa3
libmlhe.cpp
/*----------------------------------------------------------------------------

Copyright (c) 2018 J.L. Lisani (joseluis.lisani@uib.es)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

----------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>


#include "libmlhe.h"

//Class constructor
mlhe::mlhe(unsigned char *Iinput, int w, int h): w(w), h(h)
{
    //initialize output image
    I=new unsigned char[w*h];
    memcpy(I, Iinput, w*h);
    
    //auxiliary variable
    marked = new bool[w*h];
}

//Class destructor
mlhe::~mlhe()
{
    delete[] I;
    delete[] marked;
}

//Return processed image
unsigned char *mlhe::get_result()
{
    unsigned char *Iout=new unsigned char[w*h];
    memcpy(Iout, I, w*h);
    return Iout;
}

//First call to recursive algorithm (Algorithm 2)
void mlhe::apply_algorithm(int L, int A, float rrmax, float rrmin, int option,
                           int PAE_N, float PAE_smin, float PAE_smax,
                           float CLAHE_clip)
{
    Lmax = L;
    Amin = A;
    rmax = rrmax;
    rmin = rrmin;
    optionHE = option;
    N = PAE_N;
    smin = PAE_smin;
    smax = PAE_smax;
    clip = CLAHE_clip;
    
    
    //initialize auxiliary variables
    int *selected = new int[w*h]; //list of selected pixels
    //initially all pixels are selected
    for (int n=0; n < w*h; n++) selected[n]=n;
    int nselected = w*h;
    
    //list of 'active' pixels for the  extraction of connected components
    for (int n=0; n < w*h; n++) marked[n]=false;
    
    
    //recursive_histo on selected pixels
    recursive_histo(selected, nselected, 0, 255);
    
    delete[] selected;
}


//Algorithm 7: contrast limited histogram equalization
void mlhe::clahe(int *selected, int nselected, int min, int max)
{
    float hist[256];
    float histAc[256];

    //check range of output values
    vector <int> values;
    for (int n=0; n < nselected; n++) values.push_back(I[selected[n]]);
    sort(values.begin(), values.end());
    float rangeI = (float) (values[nselected-1] - values[0]);
    if (rangeI == 0) return; //return if all the pixels have the same value

    //compute cumulative histogram of values
    memset(hist, 0, 256*sizeof(float));
    for (int n=0; n < nselected; n++) hist[I[selected[n]]]+=1.0f;
    //normalize histogram
    for (int n=0; n < 256; n++) hist[n] /= (float) nselected;
    
    //CLAHE step
    //clip values above threshold
    float pexcess = 0.0f;
    for (int n=min; n <= max; n++)
        if (hist[n] > clip) {
            pexcess += (hist[n]-clip);
            hist[n] = clip;
        }
    //distribute excess values among all levels
    float peach = pexcess / (max-min+1);
    for (int n=min; n <= max; n++) hist[n] += peach;
    
    //compute cumulative histogram of values
    histAc[0]= (float) hist[0];
    for (int n=1; n < 256; n++) histAc[n] = (float) hist[n] + histAc[n-1];  
    
    //check range of output values
    int v, vmin=255, vmax=0;
    values.clear();
    for (int n=0; n < nselected; n++) {
        //apply tone mapping (i.e. cumulative distribution function)
        v = min + (int) ((histAc[I[selected[n]]] * (float) (max-min)) + 0.5f);
        values.push_back(v);
        if (v < vmin) vmin = v;
        if (v > vmax) vmax = v;
    }
    if (vmin == vmax) return; //this can happen due to the rounding of 
                              //floats to integers
    
    //get output values
    for (int n=0; n < nselected; n++) {
        I[selected[n]] = values[n];
    }
    
    

}


//auxiliary data structure for storing linear segments used in PAE
struct pointxy {
    float x, y;
    float slopetonext;
};

//Algorithm 8: piecewise affine histogram equalization (PAE)
void mlhe::piecewise_affine_histogram_equalization(int *selected, int nselected,
                                                   int min, int max)
{
    int hist[256];
    float histAc[256];
        
    //check range of input values
    vector <int> values;
    for (int n=0; n < nselected; n++) values.push_back(I[selected[n]]);
    sort(values.begin(), values.end());
    float rangeI = (float) (values[nselected-1] - values[0]);
    if (rangeI == 0) return; //return if all the pixels have the same value
    
    //compute histogram of values
    memset(hist, 0, 256*sizeof(int));
    for (int n=0; n < nselected; n++) hist[I[selected[n]]]++;
    //compute cumulative histogram of values
    histAc[0]= (float) hist[0];
    for (int n=1; n < 256; n++) histAc[n] = (float) hist[n] + histAc[n-1];
    for (int n=0; n < 256; n++) histAc[n]/= (float) nselected;
    
    //get (x_i, y_i) points for the piecewise affine approximation of 
    //the cumulative histogram
    //initial point: (min, min)
    //separation between y_i points = 1/N, N=number of intervals (N >= 1)
    vector < pointxy > points;
    struct pointxy p;
    p.x = min;
    p.y = min;
    points.push_back(p);
    float next_y = 1.0f/(float) N;
    int i=1;
    int n=0;
    float slope;
    while (i < N) {
        for (;(n < 256) && (histAc[n] < next_y); n++);
        struct pointxy p;
        p.x = (float) n;
        p.y = (float) min + next_y * (max-min);
        if (p.x != points[i-1].x) { //not infinite slope
            slope = (p.y - points[i-1].y)/(p.x - points[i-1].x);
        } else slope = -1.0; //infinite slope
        if ((slope > smax) || (slope == -1)) 
            p.y = points[i-1].y + smax * (p.x - points[i-1].x);
        if (slope < smin)  
            p.y = points[i-1].y + smin * (p.x - points[i-1].x);
        points.push_back(p);
        i++;
        next_y += 1.0f/(float) N;
    }
    //add last interval with x_i=max
    p.x = (float) max;
    p.y = (float) max;
    if (p.x != points[i-1].x) { //not infinite slope
        slope = (p.y - points[i-1].y)/(p.x - points[i-1].x);
    } else slope = -1.0; //infinite slope
    if ((slope > smax) || (slope == -1)) 
        p.y = points[i-1].y + smax * (p.x - points[i-1].x);
    if (slope < smin)  
        p.y = points[i-1].y + smin * (p.x - points[i-1].x);
    points.push_back(p);
    //check that final value is = max
    //reject if > max, since it implies saturation
    //reject if < max since it implies that the dynamic range is not fully used
    //(it may happen when too few values are in the cc)
    if (p.y > max) {
        //rescale all y values so that final value=max, but keeping 
        //the initial value = min
        for (int k = 0; k < points.size(); k++) 
            points[k].y = (float) min + (points[k].y - (float) min) * \
                          ((float) max - (float) min)/(p.y - (float) min);
    }
    if (p.y < max) return;
        
    
    //assign corresponding interval to each value 
    //(assign index to initial point of interval)
    int interval[256];
    for (int i=0; i < points.size()-1; i++) {
        int i_initial = (int) (points[i].x + 0.5f);
        int i_final = (int) (points[i+1].x + 0.5f);
        for (int k=i_initial; k <= i_final; k++) interval[k] = i;
    }
    
    //add slope information
    for (int i=0; i < points.size(); i++) {
        if (i < points.size()-1) {
            if (points[i+1].x != points[i].x) { //not infinite slope
                points[i].slopetonext = \
                    (points[i+1].y - points[i].y)/(points[i+1].x - points[i].x);
            } else points[i].slopetonext = -1.0; //infinite slope
        } else points[i].slopetonext=0; //last point
    }
    
    //Apply transform
    float rangeF;
    int v, vmin=255, vmax=0;
    values.clear();
    for (int n=0; n < nselected; n++) {
        int i = interval[I[selected[n]]]; //get interval
        v = (int) ( points[i].y + points[i].slopetonext * \
            ( (float) I[selected[n]] - points[i].x ) + 0.5f);
        values.push_back(v);
        if (v < vmin) vmin = v;
        if (v > vmax) vmax = v;
    }
    if (vmin == vmax) return; //this can happen due to the rounding of 
                              //floats to integers
    
    //get output values
    for (int n=0; n < nselected; n++) {
        I[selected[n]] = values[n];
    }

}



//Algorithm 5: classical histogram equalization
void mlhe::histogram_equalization(int *selected, 
                                  int nselected, int min, int max)
{
    int hist[256];
    float histAc[256];

    //check range of input values
    vector <int> values;
    for (int n=0; n < nselected; n++) values.push_back(I[selected[n]]);
    sort(values.begin(), values.end());
    float rangeI = (float) (values[nselected-1] - values[0]);
    if (rangeI == 0) return; //return if all the pixels have the same value

    //compute histogram of values
    memset(hist, 0, 256*sizeof(int));
    for (int n=0; n < nselected; n++) hist[I[selected[n]]]++;
    //compute cumulative histogram of values
    histAc[0]= (float) hist[0];
    for (int n=1; n < 256; n++) histAc[n] = (float) hist[n] + histAc[n-1];
    for (int n=0; n < 256; n++) histAc[n]/= (float) nselected;
        
    //check range of output values
    float rangeF;
    int v, vmin=255, vmax=0;
    values.clear();
    for (int n=0; n < nselected; n++) {
        //apply tone mapping (i.e. cumulative distribution function)
        v = min + (int) ((histAc[I[selected[n]]] * (float) (max-min)) + 0.5f);
        values.push_back(v);
        if (v < vmin) vmin = v;
        if (v > vmax) vmax = v;
    }
    
    float ratioranges;
    if (vmin != vmax) { //vmin = vmax can happen due to the rounding of 
                        //floats to integers
    
        rangeF = (float) (vmax-vmin);

        ratioranges = (rangeI != 0)?(rangeF/rangeI):(-1);
        
        //check ratio between original and processed ranges of values
        if ((ratioranges != -1) && (ratioranges <= rmax) && \
            (ratioranges >= rmin)) {
            //get output values
            for (int n=0; n < nselected; n++) {
                I[selected[n]] = values[n];
            }
        }
    
    }
    
}


//Algorithm 4 (lines 8 to 15): fill connected component
void mlhe::fill_cc(vector<int> &cc, int p0)
{
    //recursively grow connected component, use 4-connectivity
    int p = p0;
    marked[p] = false; //remove from list so it is not used again
    bool grow = true;
    queue < int > listtogrow;
    int k=1;
    while (grow) {
        cc.push_back(p); 
        int x = p%w;
        int y = p/w;
        if ((x > 0) && (marked[x-1+y*w])) { //add left neighbor to list 
            listtogrow.push(x-1+y*w); 
            marked[x-1+y*w] = false;
        }
        if ((x+1 < w) && (marked[x+1+y*w])) { //add right neighbor to list 
            listtogrow.push(x+1+y*w); 
            marked[x+1+y*w] = false; 
        }
        if ((y > 0) && (marked[x+(y-1)*w])) { //add upper neighbor to list 
            listtogrow.push(x+(y-1)*w); 
            marked[x+(y-1)*w] = false; 
        }
        if ((y+1 < h) && (marked[x+(y+1)*w])) { //add lower neighbor to list 
            listtogrow.push(x+(y+1)*w); 
            marked[x+(y+1)*w] = false; 
        }
        grow = !listtogrow.empty();
        if (grow) {
            p = listtogrow.front(); //get first element of the list
            listtogrow.pop(); //remove from list
        }
    } 
}

//Algorithm 3: 
// - get bi-level sets
// - extract connected components (Algorithm 4)
// - apply Algorithm 2 to connected components (use recursivity)
void mlhe::process_bilevelset(int *selected, int nselected, 
                              int min, int max)
{
    //mark pixels belonging to the level set
    for (int n=0; n < nselected; n++)
        if ((I[selected[n]] >= min) && (I[selected[n]] <= max)) 
            marked[selected[n]]=true;
   
    //get connected components of level set (Algorithm 4: lines 1 to 7)
    vector< vector<int> > ccs; //a list of lists of pixels
    for (int n=0; n < nselected; n++) 
        if (marked[selected[n]]) {
            //new connected component: a list of pixels 
            vector<int> cc;
            fill_cc(cc, selected[n]);
            ccs.push_back(cc);
        }
    
   //apply recursive histo equalization to each connected component
   for (int i=0; i < ccs.size(); i++) {
        //list of selected pixels for further histogram equalization
        int nselected2 = ccs[i].size();
        if (nselected2 >= Amin) { //do not process small connected components
            int *selected2 = new int[nselected2];
            for (int n = 0; n < nselected2; n++) selected2[n] = ccs[i][n];
            //recursive_histo on selected pixels
            recursive_histo(selected2, nselected2, min, max);
            delete[] selected2;
        }
    }

}

//Algorithm 2: 
// - apply histogram equalization in range [min, max]
// - process bi-level set [min, (min+max)/2]
// - process bi-level set [(min+max)/2 + 1, max]
void mlhe::recursive_histo(int *selected, int nselected,
                           int min, int max)
{

    int level = (int) (log2(256.0 / (double) (max-min+1)) + 0.5);

    //histogram equalization
    switch (optionHE) {
        case 1: //classic method (Algorithm 5)
            histogram_equalization(selected, nselected, min, max);
            break;
        
        case 2: //PAE method (Algorithm 8)
            piecewise_affine_histogram_equalization(selected, nselected, min, max);
            break;
        
        case 3: //CLAHE-based method (Algorithm 7)
            clahe(selected, nselected, min, max);
            break;
            
        default: //this should never happen
            fprintf(stderr, "Invalid Equalization Option\n");
            exit(EXIT_FAILURE);
    }
    
    if (level+1 > Lmax) return;
    
    if (max - min <= 2) return; //not enough values in range for dyadic split
    
    //center of interval of values
    int med=(min+max)/2;
    
    //Process connected components [min, med] and [med+1, max]
    process_bilevelset(selected, nselected, min, med);
    process_bilevelset(selected, nselected, med+1, max);
     
}





back to top

Software Heritage — Copyright (C) 2015–2026, The Software Heritage developers. License: GNU AGPLv3+.
The source code of Software Heritage itself is available on our development forge.
The source code files archived by Software Heritage are available under their own copyright and licenses.
Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API