Statistics
| Revision:

svn-gvsig-desktop / branches / org.gvsig.desktop-2018a / org.gvsig.desktop.compat.cdc / org.gvsig.fmap.mapcontext / org.gvsig.fmap.mapcontext.impl / src / main / java / org / gvsig / raster / lib / legend / impl / operations / equalization / EqualizationOperation.java @ 43862

History | View | Annotate | Download (19.9 KB)

1
/* gvSIG. Desktop Geographic Information System.
2
 *
3
 * Copyright ? 2007-2017 gvSIG Association
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License
7
 * as published by the Free Software Foundation; either version 2
8
 * of the License, or (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18
 * MA  02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us
21
 * at info AT gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.raster.lib.legend.impl.operations.equalization;
24

    
25
import java.util.ArrayList;
26
import java.util.Iterator;
27
import java.util.List;
28

    
29
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
30
import org.gvsig.raster.lib.buffer.api.Band;
31
import org.gvsig.raster.lib.buffer.api.BufferLocator;
32
import org.gvsig.raster.lib.buffer.api.BufferManager;
33
import org.gvsig.raster.lib.buffer.api.NoData;
34
import org.gvsig.raster.lib.buffer.api.exceptions.BandException;
35
import org.gvsig.raster.lib.buffer.api.exceptions.BufferException;
36
import org.gvsig.raster.lib.buffer.api.exceptions.BufferOperationException;
37
import org.gvsig.raster.lib.buffer.api.operations.OperationFactory;
38
import org.gvsig.raster.lib.buffer.api.statistics.HistogramBand;
39
import org.gvsig.raster.lib.buffer.api.statistics.Statistics;
40
import org.gvsig.raster.lib.buffer.spi.exceptions.ProcessingOperationException;
41
import org.gvsig.raster.lib.legend.api.RasterLegendLocator;
42
import org.gvsig.raster.lib.legend.api.RasterLegendManager;
43
import org.gvsig.raster.lib.legend.api.colorinterpretation.ColorInterpretation;
44
import org.gvsig.raster.lib.legend.spi.AbstractColoredOperation;
45
import org.gvsig.tools.locator.LocatorException;
46

    
47

    
48
/**
49
 * @author fdiaz
50
 *
51
 */
52
public class EqualizationOperation extends AbstractColoredOperation{
53

    
54
    static public String STATISTICS_PARAM = "statistics";
55

    
56

    
57
    private Statistics statistics;
58
    private List<Integer> bandsToProcess;
59
    private RowProcessor[] rowProcessors;
60
    private HistogramBand[] histogramBands;
61
    private Integer dataType;
62
    private long[][] lahe;
63
    private long[][] laheNegative;
64
    private int nElements;
65

    
66

    
67

    
68
    /**
69
     * @param factory
70
     *
71
     */
72
    public EqualizationOperation(OperationFactory factory) {
73
        super(factory);
74
    }
75

    
76
    @Override
77
    public void preProcess() throws BufferOperationException {
78
        super.preProcess();
79
        BufferManager manager = BufferLocator.getBufferManager();
80
        RasterLegendManager legendManager = RasterLegendLocator.getRasterLegendManager();
81

    
82
        statistics = (Statistics) this.getParameter(STATISTICS_PARAM,null);
83
        if (statistics==null) {
84
            statistics = this.getInputBuffer().getStatistics(null);
85
        }
86
        histogramBands = statistics.getHistogram();
87

    
88
        int bands = this.getInputBuffer().getBandCount();
89
        NoData[] noData = this.getInputBuffer().getBandNoData();
90

    
91

    
92
        if (mustCopyUnprocessedBands()) {
93
            this.setParameter(OUTPUT_COLOR_INTERPRETATION_PARAM, getInputColorInterpretation());
94
            noData = this.getInputBuffer().getBandNoData();
95
            try {
96
                this.setOutputBuffer(
97
                    manager.createBuffer(
98
                            this.getInputBuffer().getRows(), 
99
                            this.getInputBuffer().getColumns(), 
100
                            this.getInputBuffer().getBandTypes(),
101
                            this.getInputBuffer().getBandNoData(), 
102
                            this.getInputBuffer().getProjection(), 
103
                            this.getInputBuffer().getEnvelope()));
104
            } catch (LocatorException | BufferException | CreateEnvelopeException e) {
105
                throw new ProcessingOperationException(e);
106
            }
107
        } else {
108
            List<String> colorInterpretations = new ArrayList<>();
109
            List<NoData> noDatas = new ArrayList<>();
110
            List<Integer> types = new ArrayList<>();
111
            for (int band = 0; band < bands; band++) {
112
                if (isProcessableBand(band)) {
113
                    colorInterpretations.add(getInputColorInterpretation().get(band));
114
                    noDatas.add(this.getInputBuffer().getBandNoData()[band]);
115
                    types.add(this.getInputBuffer().getBandTypes()[band]);
116
                }
117
            }
118
            if (getInputColorInterpretation().hasAlphaBand()) {
119
                colorInterpretations.add(ColorInterpretation.ALPHA_BAND);
120
            }
121
            setOutputColorInterpretation(
122
                    legendManager.createColorInterpretation(colorInterpretations));
123
            this.setParameter(OUTPUT_COLOR_INTERPRETATION_PARAM, getOutputColorInterpretation());
124
            try {
125
                this.setOutputBuffer(
126
                    manager.createBuffer(
127
                            this.getInputBuffer().getRows(), 
128
                            this.getInputBuffer().getColumns(), 
129
                            this.getTypesAsArray(types),
130
                            this.getNoDatasAsArray(noDatas), 
131
                            this.getInputBuffer().getProjection(), 
132
                            this.getInputBuffer().getEnvelope()));
133
            } catch (Exception e) {
134
                throw new ProcessingOperationException(e);
135
            }
136
        }
137

    
138
        rowProcessors = new RowProcessor[bands];
139
        dataType = null;
140
        if(getBandsToProcess().isEmpty()){
141
            throw new ProcessingOperationException("There is no RGB band.", null);
142
        }
143
        for (Iterator<Integer> iterator = getBandsToProcess().iterator(); iterator.hasNext();) {
144
            Integer band = iterator.next();
145
            int bandType = this.getInputBuffer().getBand(band).getDataType();
146
            if(dataType!=null) {
147
                if(dataType!=bandType){
148
                    throw new IllegalArgumentException("All bands must be of same data type.");
149
                }
150
            } else {
151
                dataType=bandType;
152
            }
153
            switch (bandType) {
154
            case BufferManager.TYPE_BYTE:
155
                rowProcessors[band] = new ByteRowProcessor(band);
156
                break;
157
            case BufferManager.TYPE_USHORT:
158
                rowProcessors[band] = new UShortRowProcessor(band);
159
                break;
160
            case BufferManager.TYPE_SHORT:
161
                rowProcessors[band] = new ShortRowProcessor(band);
162
                break;
163
            case BufferManager.TYPE_INT:
164
                rowProcessors[band] = new IntRowProcessor(band);
165
                break;
166
            case BufferManager.TYPE_FLOAT: //Can't process float band
167
                rowProcessors[band] = null; //new FloatRowProcessor(band);
168
                break;
169
            case BufferManager.TYPE_DOUBLE: //Can't process double band
170
                rowProcessors[band] = null; //new DoubleRowProcessor(band);
171
                break;
172
            default:
173
                throw new IllegalArgumentException("Can't process type of band '" + band + "'");
174
            }
175
        }
176

    
177
        double[][] accumNormalize = convertTableToNormalizeAccumulate(getHistogramTable());
178
        double[][] accumNormalizeNeg = convertTableToNormalizeAccumulate(getNegativeHistogramTable());
179

    
180
        int maxValue = 255;
181
        switch (dataType) {
182
        case BufferManager.TYPE_BYTE:
183
            maxValue = 255;
184
            break;
185
        case BufferManager.TYPE_SHORT:
186
            maxValue = Short.MAX_VALUE;
187
            break;
188
        case BufferManager.TYPE_USHORT:
189
            maxValue = Math.abs(Short.MIN_VALUE) + Short.MAX_VALUE;
190
            break;
191
        case BufferManager.TYPE_INT:
192
            maxValue = Integer.MAX_VALUE;
193
            break;
194
        default:
195
            throw new IllegalArgumentException("Can't process type '" + dataType + "'");
196
        }
197

    
198
        lahe = lahe(accumNormalize, maxValue);
199
        laheNegative = lahe(accumNormalizeNeg, maxValue);
200
        nElements = (laheNegative[0].length - 1); //FIXME: ???
201

    
202
    }
203

    
204

    
205
    /**
206
     * Obtiene la tabla completa del histograma
207
     * @return long[][]
208
     */
209
    private long[][] getHistogramTable() {
210
        int size;
211
        int rangeMin;
212
        int rangeMax;
213
        switch (dataType) {
214
        case BufferManager.TYPE_BYTE:
215
            rangeMin = 0;
216
            rangeMax = 255;
217
            size = 256;
218
            break;
219
        case BufferManager.TYPE_SHORT:
220
            rangeMin = Short.MIN_VALUE;
221
            rangeMax = Short.MAX_VALUE;
222
            size = rangeMax-rangeMin+1;
223
            break;
224
        case BufferManager.TYPE_USHORT:
225
            rangeMin = 0;
226
            rangeMax = Math.abs(Short.MIN_VALUE) + Short.MAX_VALUE;
227
            size = rangeMax-rangeMin+1;
228
            break;
229
        case BufferManager.TYPE_INT:
230
            rangeMin = Integer.MIN_VALUE;
231
            rangeMax = Integer.MAX_VALUE;
232
            size = rangeMax-rangeMin+1;
233
            break;
234
        default:
235
            throw new IllegalArgumentException("Can't process type '" + dataType + "'");
236
        }
237
        long[][] table = new long[getBandsToProcess().size()][size];
238
        for (int i = 0; i < table.length; i++) {
239
            System.arraycopy(histogramBands[getBandsToProcess().get(i)].getFrequencies(), 0, table[i], 0, size);
240
        }
241
        return table;
242
    }
243

    
244
    /**
245
     * Obtiene el histograma de la imagen negativa.
246
     * @return long[][]
247
     */
248
    private long[][] getNegativeHistogramTable() {
249
        long[][] table = getHistogramTable();
250
        long[][] tableNeg = new long[table.length][table[0].length];
251
        for (int i = 0; i < tableNeg.length; i++){
252
            for (int j = 0; j < tableNeg[i].length; j++){
253
                tableNeg[i][table[i].length - j - 1] = table[i][j];
254
            }
255
        }
256
        return tableNeg;
257
    }
258

    
259
    /**
260
     * Obtiene la tabla de valores normalizada. Divide todos los elementos por el n?mero de
261
     * pixeles total.
262
     * @return tabla de valores normalizada
263
     */
264
    private static double[][] convertToNormalizeHistogram(long[][] tableToConvert) {
265
        long[] nValues = new long[tableToConvert.length];
266
        for (int i = 0; i < tableToConvert.length; i++) {
267
            for (int j = 0; j < tableToConvert[i].length; j++) {
268
                nValues[i] += tableToConvert[i][j];
269
            }
270
        }
271

    
272
        double[][] res = new double[tableToConvert.length][tableToConvert[0].length];
273
        for (int i = 0; i < tableToConvert.length; i++) {
274
            for (int j = 0; j < tableToConvert[i].length; j++) {
275
                res[i][j] = (double)((double)tableToConvert[i][j] / (double)nValues[i]);
276
            }
277
        }
278

    
279
        return res;
280
    }
281

    
282
    /**
283
     * Obtiene la tabla de valores normalizada y acumulada. Divide todos los elementos por el n?mero de
284
     * pixeles total.
285
     * @return tabla de valores normalizada
286
     */
287
    private static double[][] convertTableToNormalizeAccumulate(long[][] tableToConvert) {
288
        double[][] res = convertToNormalizeHistogram(tableToConvert);
289
        for (int i = 0; i < tableToConvert.length; i++) {
290
            for (int j = 0; j < tableToConvert[i].length; j++) {
291
                if(j > 0) {
292
                    res[i][j] += res[i][j - 1];
293
                }
294
            }
295
        }
296
        return res;
297
    }
298

    
299

    
300

    
301

    
302

    
303

    
304
    @Override
305
    public void process() throws ProcessingOperationException {
306
        super.process();
307
        for (int band=0; band<this.getInputBuffer().getBandCount(); band++){
308
            if (getBandsToProcess().contains(band)) {
309
                Band bufferBand = this.getInputBuffer().getBand(band);
310
                Band outputBufferBand = this.getOutputBuffer().getBand(band);
311

    
312
                for (int row = 0; row < this.getInputBuffer().getRows(); row++) {
313
                    Object rowBuffer = bufferBand.createRowBuffer();
314
                    bufferBand.fetchRow(row, rowBuffer);
315

    
316
                    Object outputRowBuffer = outputBufferBand.createRowBuffer();
317
                    outputBufferBand.fetchRow(row, outputRowBuffer);
318

    
319
                    rowProcessors[band].processRow(rowBuffer, outputRowBuffer);
320

    
321
                    outputBufferBand.putRow(row, outputRowBuffer);
322
                }
323
            } else {
324
                try {
325
                    this.getOutputBuffer().getBand(band).copyFrom(this.getInputBuffer().getBand(band));
326
                } catch (BandException e) {
327
                    throw new ProcessingOperationException(e);
328
                }
329
            }
330
        }
331
    }
332

    
333
    @Override
334
    public void postProcess() throws BufferOperationException {
335
        super.postProcess();
336
    }
337

    
338
    /**
339
     * M?todo lahe para la ecualizaci?n. Cada posici?n del array resultante tendr? el valor de salida para
340
     * un valor de entrada dado.
341
     * @param accumNorm Histograma acumulado
342
     * @param value Valor m?ximo
343
     * @return
344
     */
345
    private long[][] lahe(double[][] accumNorm, int value) {
346
        long[][] res = new long[accumNorm.length][accumNorm[0].length];
347
        for (int i = 0; i < res.length; i++){
348
              for (int j = 0; j < res[i].length; j++){
349
                  res[i][j] = Math.round(accumNorm[i][j] * value);
350
              }
351
        }
352
        return res;
353
    }
354

    
355
    /**
356
     * @param band
357
     * @return
358
     */
359
    private List<Integer> getBandsToProcess() {
360
        if(bandsToProcess == null){
361
            int bands = this.getInputBuffer().getBandCount();
362
            bandsToProcess = new ArrayList<>();
363

    
364
            for (int band = 0; band < bands; band++) {
365
                if (isProcessableBand(band)) {
366
                    bandsToProcess.add(band);
367
                }
368
            }
369
        }
370
        return bandsToProcess;
371
    }
372

    
373
    /**
374
     * @param band
375
     * @return
376
     */
377
    @Override
378
    protected boolean isProcessableBand(int band) {
379
        return isRGBBand(band) && (this.getInputBuffer().getBandTypes()[band] == BufferManager.TYPE_BYTE
380
            || this.getInputBuffer().getBandTypes()[band] == BufferManager.TYPE_SHORT
381
            || this.getInputBuffer().getBandTypes()[band] == BufferManager.TYPE_USHORT
382
            || this.getInputBuffer().getBandTypes()[band] == BufferManager.TYPE_INT
383
        );
384
    }
385

    
386
    private boolean isRGBBand(int band) {
387
        String bandColorInterpretation = getInputColorInterpretation().get(band);
388
        return (bandColorInterpretation.equals(ColorInterpretation.RED_BAND) ||
389
            bandColorInterpretation.equals(ColorInterpretation.GREEN_BAND) ||
390
            bandColorInterpretation.equals(ColorInterpretation.BLUE_BAND));
391
    }
392

    
393

    
394
    interface RowProcessor {
395
        void processRow(Object inputRow, Object outputRow);
396
        Number processValue(Object value);
397
    };
398

    
399
    private abstract class AbstractRowProcessor implements RowProcessor {
400
        protected int band;
401
        protected HistogramBand histogram;
402
        protected NoData noData;
403

    
404
        public AbstractRowProcessor(int band) {
405
            this.band = band;
406
            this.histogram = statistics.getHistogram()[this.band];
407
            this.noData = getInputBuffer().getBand(this.band).getNoData();
408
        }
409
    }
410

    
411
    private class ByteRowProcessor extends AbstractRowProcessor {
412

    
413

    
414
        public ByteRowProcessor(int band) {
415
            super(band);
416
        }
417

    
418
        @Override
419
        public void processRow(Object inputRow, Object outputRow) {
420
            byte[] inputByteRow = (byte[])inputRow;
421
            byte[] outputByteRow = (byte[])outputRow;
422

    
423
            for (int i = 0; i < inputByteRow.length; i++) {
424
                outputByteRow[i] = processValue(inputByteRow[i]).byteValue();
425
            }
426
        }
427

    
428
        @Override
429
        public Number processValue(Object value) {
430
            if(noData.isDefined() && noData.getValue().equals(value)){
431
                return (byte)value;
432
            }
433

    
434
            //M?todo lahe
435
            int intValue = 0xFF & ((Byte) value);
436
            int equalizationPositive = (int)lahe[getBandsToProcess().indexOf(band)][intValue % (int)histogram.getNumValues()];
437
            int equalizationNegative = (int)laheNegative[getBandsToProcess().indexOf(band)][nElements - (intValue % (int)histogram.getNumValues())];
438

    
439
            int resValue = ((nElements - equalizationNegative) + equalizationPositive) / 2;
440
            return (byte)(resValue & 0x000000ff);
441
        }
442
    }
443

    
444
    private class ShortRowProcessor extends AbstractRowProcessor {
445

    
446
        public ShortRowProcessor(int band) {
447
            super(band);
448
        }
449

    
450
        @Override
451
        public void processRow(Object inputRow, Object outputRow) {
452
            short[] inputByteRow = (short[])inputRow;
453
            short[] outputByteRow = (short[])outputRow;
454
            for (int i = 0; i < inputByteRow.length; i++) {
455
                outputByteRow[i] = processValue(inputByteRow[i]).shortValue();
456
            }
457
        }
458

    
459
        @Override
460
        public Number processValue(Object value) {
461
            if(noData.isDefined() && noData.getValue().equals(value)){
462
                return (byte)0;
463
            }
464

    
465
            //M?todo lahe
466
            int intValue = ((Short) value).byteValue();
467
            int equalizationPositive = (int)lahe[getBandsToProcess().indexOf(band)][intValue % (int)histogram.getNumValues()];
468
            int equalizationNegative = (int)laheNegative[getBandsToProcess().indexOf(band)][nElements - (intValue % (int)histogram.getNumValues())];
469

    
470
            int resValue = ((nElements - equalizationNegative) + equalizationPositive) / 2;
471
            return (short)resValue;
472
        }
473

    
474
    }
475

    
476
    private class UShortRowProcessor extends AbstractRowProcessor {
477

    
478
        public UShortRowProcessor(int band) {
479
            super(band);
480
        }
481

    
482
        @Override
483
        public void processRow(Object inputRow, Object outputRow) {
484
            short[] inputByteRow = (short[])inputRow;
485
            short[] outputByteRow = (short[])outputRow;
486
            for (int i = 0; i < inputByteRow.length; i++) {
487
                outputByteRow[i] = processValue(inputByteRow[i]).shortValue();
488
            }
489
        }
490

    
491
        @Override
492
        public Number processValue(Object value) {
493
            if(noData.isDefined() && noData.getValue().equals(value)){
494
                return (byte)0;
495
            }
496

    
497
            //M?todo lahe
498
            int intValue = 0xFFFF & ((Short) value).byteValue();
499
            int equalizationPositive = (int)lahe[getBandsToProcess().indexOf(band)][intValue % (int)histogram.getNumValues()];
500
            int equalizationNegative = (int)laheNegative[getBandsToProcess().indexOf(band)][nElements - (intValue % (int)histogram.getNumValues())];
501

    
502
            int resValue = ((nElements - equalizationNegative) + equalizationPositive) / 2;
503
            return (short)resValue;
504
        }
505

    
506
    }
507

    
508
    private class IntRowProcessor extends AbstractRowProcessor {
509

    
510
        public IntRowProcessor(int band) {
511
            super(band);
512
        }
513

    
514
        @Override
515
        public void processRow(Object inputRow, Object outputRow) {
516
            int[] inputByteRow = (int[])inputRow;
517
            int[] outputByteRow = (int[])outputRow;
518
            for (int i = 0; i < inputByteRow.length; i++) {
519
                outputByteRow[i] = processValue(inputByteRow[i]).intValue();
520
            }
521
        }
522

    
523
        @Override
524
        public Number processValue(Object value) {
525
            if(noData.isDefined() && noData.getValue().equals(value)){
526
                return (byte)0;
527
            }
528

    
529
            Double dValue = ((Number) value).doubleValue();
530

    
531
            //M?todo lahe
532
            int intValue = ((Integer) value);
533
            int equalizationPositive = (int)lahe[getBandsToProcess().indexOf(band)][intValue % (int)histogram.getNumValues()];
534
            int equalizationNegative = (int)laheNegative[getBandsToProcess().indexOf(band)][nElements - (intValue % (int)histogram.getNumValues())];
535

    
536
            int resValue = ((nElements - equalizationNegative) + equalizationPositive) / 2;
537
            return (int)resValue;
538
        }
539
    }
540
}