MDIN stands for Moment Distance Index Normalized, originally developed by Salas and Henebry (2013) and improved by Salas and Subburayalu (2019). We developed MDIN to improve the spatial-spectral classification of hyperspectral data for agricultural management systems. MDIN analyzes the full spectral curve across different wavelengths.
How it Works:
(1) Distance Calculation: The index calculates the distance of each band’s reflectance from two “pivots” within the spectral curve: the left pivot and the right pivot. (2) Normalization: These distances are normalized to create a value between -1 and 1.
Below is the simplified code for calculating MDIN using a Sentinel-2 image on the Google Earth Engine (GEE) platform.
//Get the boudary of your study area.
var ohio = ee.FeatureCollection('TIGER/2018/States')
.filter(ee.Filter.eq('NAME', 'Ohio'));
// Function to calculate MDIN for Sentinel-2 imagery.
// If you want to use LandSat bands, make sure to change/edit the bands below.
var calculateMDIN = function(image) {
// Define wavelengths for Sentinel-2 bands (in nanometers)
var wavelengths = {
'B2': 490, // Blue (LP - Left Pivot)
'B3': 560, // Green
'B4': 665, // Red
'B5': 705, // Red Edge 1
'B6': 740, // Red Edge 2
'B7': 783, // Red Edge 3
'B8': 842, // NIR
'B8A': 865, // Red Edge 4
'B11': 1610, // SWIR 1
'B12': 2190 // SWIR 2 (RP - Right Pivot)
};
// Get all bands for calculation
var allBands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B8A', 'B11', 'B12'];
// Calculate MDLP (Left Pivot to Right Pivot)
// If using LandSat, change Sentinel-2 [B2] with the first band of LandSat.
var mdlpSum = ee.Image(0);
allBands.forEach(function(band) {
var reflectance = image.select(band).subtract(image.select(allBands).reduce(ee.Reducer.mean())); // Centralize reflectance
var wavelengthPosition = ee.Number(wavelengths[band]);
var leftPivotDiff = wavelengthPosition.subtract(wavelengths['B2']); // Distance from LP
// Hypotenuse: sqrt(ρ² + (i-λLP)²)
var distanceLP = reflectance.pow(2).add(leftPivotDiff.pow(2)).sqrt();
mdlpSum = mdlpSum.add(distanceLP);
});
// Calculate MDRP (Right Pivot to Left Pivot)
// If using LandSat, change Sentinel-2 [B12] with the last band of LandSat.
var mdrpSum = ee.Image(0);
allBands.forEach(function(band) {
var reflectance = image.select(band).subtract(image.select(allBands).reduce(ee.Reducer.mean())); // Centralize reflectance
var wavelengthPosition = ee.Number(wavelengths[band]);
var rightPivotDiff = ee.Number(wavelengths['B12']).subtract(wavelengthPosition); // Distance from RP
// Hypotenuse: sqrt(ρ² + (λRP-i)²)
var distanceRP = reflectance.pow(2).add(rightPivotDiff.pow(2)).sqrt();
mdrpSum = mdrpSum.add(distanceRP);
});
// Normalize MDLP and MDRP
var normalizedMDLP = mdlpSum.divide(mdlpSum.add(mdrpSum));
var normalizedMDRP = mdrpSum.divide(mdlpSum.add(mdrpSum));
// Calculate MDIN = (MDRP - MDLP)/(MDRP + MDLP)
var mdin = normalizedMDRP.subtract(normalizedMDLP).rename('MDIN');
return image.addBands(mdin);
};
// Get Sentinel-2 collection for summer 2023. Change the dates depending on your study dates.
var s2Collection = ee.ImageCollection('COPERNICUS/S2_SR')
.filterDate('2023-05-01', '2023-06-30')
.filterBounds(ohio.geometry())
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10));
// Calculate MDIN for each image
var mdinCollection = s2Collection.map(calculateMDIN);
// Calculate the mean MDIN across all images
var meanMDIN = mdinCollection.select('MDIN').mean();
// Clip to Ohio boundary
var mdinOhio = meanMDIN.clip(ohio);
// Calculate MDIN statistics for visualization
var mdinStats = mdinOhio.reduceRegion({
reducer: ee.Reducer.max(),
geometry: ohio.geometry(),
scale: 1000,
maxPixels: 1e9
});
// Get the maximum MDIN value
var maxMDIN = ee.Number(mdinStats.get('MDIN'));
print('Maximum MDIN:', maxMDIN);
// Create a palette with 20 colors
var palette = [
'#0000FF', '#0033FF', '#0066FF', '#0099FF', '#00CCFF',
'#00FFFF', '#33FFCC', '#66FF99', '#99FF66', '#CCFF33',
'#FFFF00', '#FFCC00', '#FF9900', '#FF6600', '#FF3300',
'#FF0000', '#CC0033', '#990066', '#660099', '#3300CC'
];
// Visualization parameters with 20 categories
var mdinVis = {
min: 0,
max: maxMDIN.getInfo(),
palette: palette
};
// Center map on Ohio
Map.centerObject(ohio, 7);
// Add layers
Map.addLayer(mdinOhio, mdinVis, 'Average Summer MDIN Ohio');
Map.addLayer(ohio.style({color: 'black', fillColor: '00000000'}), {}, 'Ohio boundary');
// Add a legend for the 20 categories
var createCategoricalLegend = function(palette, min, max, categories) {
var legend = ui.Panel({
style: {
position: 'bottom-right',
padding: '8px 15px'
}
});
// Legend title
var legendTitle = ui.Label({
value: 'MDIN Categories',
style: {
fontWeight: 'bold',
fontSize: '16px',
margin: '0 0 4px 0',
padding: '0'
}
});
legend.add(legendTitle);
// Create rows for each category
var step = (max - min) / categories;
for (var i = 0; i < categories; i++) {
var value = (min + i * step).toFixed(2) + ' to ' + (min + (i + 1) * step).toFixed(2);
var colorBox = ui.Label({
style: {
backgroundColor: palette[i],
padding: '8px',
margin: '0 0 4px 0'
}
});
var description = ui.Label({
value: value,
style: {margin: '0 0 4px 6px'}
});
legend.add(ui.Panel([colorBox, description], ui.Panel.Layout.Flow('horizontal')));
}
return legend;
};
// Add the categorical legend to the map
var legend = createCategoricalLegend(palette, 0, maxMDIN.getInfo(), 20);
Map.add(legend);
// Add a chart showing MDIN values over time
var chart = ui.Chart.image.series({
imageCollection: mdinCollection.select('MDIN'),
region: ohio.geometry(),
reducer: ee.Reducer.mean(),
scale: 1000
}).setOptions({
title: 'Average MDIN Values Over Summer 2023',
vAxis: {title: 'MDIN'},
hAxis: {title: 'Date'},
trendlines: {0: {}}
});
print(chart);
If you use the code above or apply MDIN, kindly use this citation:
Salas, EAL and Subburayalu, SK (2019). Modified shape index for object-based random forest image classification of agricultural systems using airborne hyperspectral datasets. PLoS ONE 14(3): e0213356. https://doi.org/10.1371/journal.pone.0213356