diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..97fd635 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 7d31d02b5a37fef6b18a9a1ef4415024 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..be14a74 --- /dev/null +++ b/.nojekyll @@ -0,0 +1,2 @@ +# This file is present so that github will serve file from directories +# having names with a leading underscore. diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..ddc1dd0 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.spectralpython.net diff --git a/_downloads/1da6cf4d2cc3ea30e3e65a0f880f0eca/92AV3GT.GIS b/_downloads/1da6cf4d2cc3ea30e3e65a0f880f0eca/92AV3GT.GIS new file mode 100644 index 0000000..5e26cce Binary files /dev/null and b/_downloads/1da6cf4d2cc3ea30e3e65a0f880f0eca/92AV3GT.GIS differ diff --git a/_downloads/501e8b9175aee04ae19602a9b72e7fb0/92AV3C.spc b/_downloads/501e8b9175aee04ae19602a9b72e7fb0/92AV3C.spc new file mode 100644 index 0000000..1dc02ea --- /dev/null +++ b/_downloads/501e8b9175aee04ae19602a9b72e7fb0/92AV3C.spc @@ -0,0 +1,222 @@ +FILE spectral_920530-930111.ascii +--------------------------------- +400.019989 9.780000 0.920000 0.500000 2.000000 +409.820007 9.820000 0.920000 0.500000 3.000000 +419.619995 9.850000 0.930000 0.500000 4.000000 +429.429993 9.890000 0.940000 0.500000 5.000000 +439.250000 9.920000 0.950000 0.500000 6.000000 +449.070007 9.940000 0.950000 0.500000 7.000000 +458.899994 9.970000 0.960000 0.500000 8.000000 +468.730011 9.990000 0.970000 0.500000 9.000000 +478.570007 10.010000 0.970000 0.500000 10.000000 +488.410004 10.020000 0.980000 0.500000 11.000000 +498.260010 10.040000 0.990000 0.500000 12.000000 +508.119995 10.050000 1.000000 0.500000 13.000000 +517.979980 10.050000 1.000000 0.500000 14.000000 +527.849976 10.060000 1.010000 0.500000 15.000000 +537.719971 10.060000 1.020000 0.500000 16.000000 +547.599976 10.060000 1.030000 0.500000 17.000000 +557.489990 10.050000 1.030000 0.500000 18.000000 +567.380005 10.040000 1.040000 0.500000 19.000000 +577.280029 10.030000 1.050000 0.500000 20.000000 +587.179993 10.020000 1.060000 0.500000 21.000000 +597.090027 10.000000 1.060000 0.500000 22.000000 +607.010010 9.980000 1.070000 0.500000 23.000000 +616.929993 9.960000 1.080000 0.500000 24.000000 +626.849976 9.940000 1.090000 0.500000 25.000000 +636.780029 9.910000 1.090000 0.500000 26.000000 +646.719971 9.880000 1.100000 0.500000 27.000000 +656.669983 9.840000 1.110000 0.500000 28.000000 +666.609985 9.810000 1.120000 0.500000 29.000000 +676.570007 9.770000 1.120000 0.500000 30.000000 +686.530029 9.730000 1.130000 0.500000 31.000000 +696.500000 9.680000 1.140000 0.500000 32.000000 +686.909973 8.870000 0.880000 0.500000 34.000000 +696.549988 8.870000 0.880000 0.500000 35.000000 +706.190002 8.880000 0.890000 0.500000 36.000000 +715.830017 8.880000 0.890000 0.500000 37.000000 +725.469971 8.880000 0.900000 0.500000 38.000000 +735.109985 8.890000 0.900000 0.500000 39.000000 +744.739990 8.890000 0.910000 0.500000 40.000000 +754.380005 8.890000 0.910000 0.500000 41.000000 +764.010010 8.900000 0.920000 0.500000 42.000000 +773.640015 8.900000 0.920000 0.500000 43.000000 +783.270020 8.900000 0.930000 0.500000 44.000000 +792.909973 8.910000 0.930000 0.500000 45.000000 +802.530029 8.910000 0.940000 0.500000 46.000000 +812.159973 8.910000 0.940000 0.500000 47.000000 +821.789978 8.920000 0.950000 0.500000 48.000000 +831.409973 8.920000 0.950000 0.500000 49.000000 +841.039978 8.920000 0.960000 0.500000 50.000000 +850.659973 8.930000 0.960000 0.500000 51.000000 +860.280029 8.930000 0.970000 0.500000 52.000000 +869.909973 8.930000 0.970000 0.500000 53.000000 +879.530029 8.930000 0.980000 0.500000 54.000000 +889.140015 8.940000 0.980000 0.500000 55.000000 +898.760010 8.940000 0.990000 0.500000 56.000000 +908.380005 8.940000 0.990000 0.500000 57.000000 +917.989990 8.940000 0.990000 0.500000 58.000000 +927.609985 8.950000 1.000000 0.500000 59.000000 +937.219971 8.950000 1.000000 0.500000 60.000000 +946.830017 8.950000 1.010000 0.500000 61.000000 +956.450012 8.950000 1.010000 0.500000 62.000000 +966.059998 8.950000 1.020000 0.500000 63.000000 +975.659973 8.960000 1.020000 0.500000 64.000000 +985.270020 8.960000 1.030000 0.500000 65.000000 +994.880005 8.960000 1.030000 0.500000 66.000000 +1004.479980 8.960000 1.040000 0.500000 67.000000 +1014.090027 8.960000 1.040000 0.500000 68.000000 +1023.690002 8.970000 1.050000 0.500000 69.000000 +1033.290039 8.970000 1.050000 0.500000 70.000000 +1042.890015 8.970000 1.060000 0.500000 71.000000 +1052.489990 8.970000 1.060000 0.500000 72.000000 +1062.089966 8.970000 1.070000 0.500000 73.000000 +1071.689941 8.970000 1.070000 0.500000 74.000000 +1081.290039 8.970000 1.080000 0.500000 75.000000 +1090.880005 8.980000 1.080000 0.500000 76.000000 +1100.479980 8.980000 1.090000 0.500000 77.000000 +1110.069946 8.980000 1.090000 0.500000 78.000000 +1119.660034 8.980000 1.100000 0.500000 79.000000 +1129.250000 8.980000 1.100000 0.500000 80.000000 +1138.839966 8.980000 1.100000 0.500000 81.000000 +1148.430054 8.980000 1.110000 0.500000 82.000000 +1158.020020 8.980000 1.110000 0.500000 83.000000 +1167.609985 8.980000 1.120000 0.500000 84.000000 +1177.189941 8.980000 1.120000 0.500000 85.000000 +1186.770020 8.990000 1.130000 0.500000 86.000000 +1196.359985 8.990000 1.130000 0.500000 87.000000 +1205.939941 8.990000 1.140000 0.500000 88.000000 +1215.520020 8.990000 1.140000 0.500000 89.000000 +1225.099976 8.990000 1.150000 0.500000 90.000000 +1234.680054 8.990000 1.150000 0.500000 91.000000 +1244.260010 8.990000 1.160000 0.500000 92.000000 +1253.829956 8.990000 1.160000 0.500000 93.000000 +1263.410034 8.990000 1.170000 0.500000 94.000000 +1272.979980 8.990000 1.170000 0.500000 95.000000 +1282.550049 8.990000 1.180000 0.500000 96.000000 +1273.000000 9.180000 1.560000 0.700000 98.000000 +1282.959961 9.200000 1.570000 0.700000 99.000000 +1292.930054 9.220000 1.570000 0.700000 100.000000 +1302.890015 9.240000 1.580000 0.700000 101.000000 +1312.849976 9.260000 1.580000 0.700000 102.000000 +1322.810059 9.280000 1.590000 0.700000 103.000000 +1332.770020 9.300000 1.590000 0.700000 104.000000 +1342.729980 9.320000 1.600000 0.700000 105.000000 +1352.680054 9.340000 1.600000 0.700000 106.000000 +1362.640015 9.360000 1.610000 0.700000 107.000000 +1372.589966 9.370000 1.610000 0.700000 108.000000 +1382.540039 9.390000 1.620000 0.700000 109.000000 +1392.489990 9.410000 1.620000 0.700000 110.000000 +1402.439941 9.430000 1.630000 0.700000 111.000000 +1412.390015 9.440000 1.630000 0.700000 112.000000 +1422.339966 9.460000 1.640000 0.700000 113.000000 +1432.280029 9.470000 1.640000 0.700000 114.000000 +1442.229980 9.490000 1.650000 0.700000 115.000000 +1452.170044 9.510000 1.650000 0.700000 116.000000 +1462.109985 9.520000 1.660000 0.700000 117.000000 +1472.050049 9.540000 1.660000 0.700000 118.000000 +1481.989990 9.550000 1.670000 0.700000 119.000000 +1491.920044 9.570000 1.670000 0.700000 120.000000 +1501.859985 9.580000 1.680000 0.700000 121.000000 +1511.790039 9.590000 1.680000 0.700000 122.000000 +1521.729980 9.610000 1.690000 0.700000 123.000000 +1531.660034 9.620000 1.690000 0.700000 124.000000 +1541.589966 9.630000 1.700000 0.700000 125.000000 +1551.520020 9.650000 1.700000 0.700000 126.000000 +1561.439941 9.660000 1.710000 0.700000 127.000000 +1571.369995 9.670000 1.710000 0.700000 128.000000 +1581.300049 9.680000 1.720000 0.700000 129.000000 +1591.219971 9.700000 1.720000 0.700000 130.000000 +1601.140015 9.710000 1.730000 0.700000 131.000000 +1611.060059 9.720000 1.730000 0.700000 132.000000 +1620.979980 9.730000 1.740000 0.700000 133.000000 +1630.900024 9.740000 1.740000 0.700000 134.000000 +1640.810059 9.750000 1.750000 0.700000 135.000000 +1650.729980 9.760000 1.750000 0.700000 136.000000 +1660.640015 9.770000 1.760000 0.700000 137.000000 +1670.560059 9.780000 1.770000 0.700000 138.000000 +1680.469971 9.790000 1.770000 0.700000 139.000000 +1690.380005 9.800000 1.780000 0.700000 140.000000 +1700.280029 9.810000 1.780000 0.700000 141.000000 +1710.189941 9.820000 1.790000 0.700000 142.000000 +1720.099976 9.820000 1.790000 0.700000 143.000000 +1730.000000 9.830000 1.800000 0.700000 144.000000 +1739.900024 9.840000 1.800000 0.700000 145.000000 +1749.810059 9.850000 1.810000 0.700000 146.000000 +1759.709961 9.850000 1.810000 0.700000 147.000000 +1769.599976 9.860000 1.820000 0.700000 148.000000 +1779.500000 9.870000 1.820000 0.700000 149.000000 +1789.400024 9.870000 1.830000 0.700000 150.000000 +1799.290039 9.880000 1.830000 0.700000 151.000000 +1809.189941 9.890000 1.840000 0.700000 152.000000 +1819.079956 9.890000 1.840000 0.700000 153.000000 +1828.969971 9.900000 1.850000 0.700000 154.000000 +1838.859985 9.900000 1.850000 0.700000 155.000000 +1848.750000 9.910000 1.860000 0.700000 156.000000 +1858.630005 9.910000 1.860000 0.700000 157.000000 +1868.520020 9.910000 1.870000 0.700000 158.000000 +1878.400024 9.920000 1.870000 0.700000 159.000000 +1888.280029 9.920000 1.880000 0.700000 160.000000 +1883.239990 13.720000 2.250000 1.850000 162.000000 +1893.250000 13.750000 2.250000 1.850000 163.000000 +1903.260010 13.790000 2.260000 1.850000 164.000000 +1913.260010 13.820000 2.260000 1.850000 165.000000 +1923.270020 13.850000 2.270000 1.850000 166.000000 +1933.270020 13.880000 2.280000 1.850000 167.000000 +1943.270020 13.910000 2.280000 1.850000 168.000000 +1953.260010 13.950000 2.290000 1.850000 169.000000 +1963.250000 13.970000 2.290000 1.850000 170.000000 +1973.239990 14.000000 2.300000 1.850000 171.000000 +1983.229980 14.030000 2.310000 1.850000 172.000000 +1993.219971 14.060000 2.310000 1.850000 173.000000 +2003.199951 14.090000 2.320000 1.850000 174.000000 +2013.180054 14.110000 2.320000 1.850000 175.000000 +2023.160034 14.140000 2.330000 1.850000 176.000000 +2033.130005 14.160000 2.340000 1.850000 177.000000 +2043.099976 14.190000 2.340000 1.850000 178.000000 +2053.070068 14.210000 2.350000 1.850000 179.000000 +2063.040039 14.230000 2.350000 1.850000 180.000000 +2073.000000 14.260000 2.360000 1.850000 181.000000 +2082.969971 14.280000 2.370000 1.850000 182.000000 +2092.919922 14.300000 2.370000 1.850000 183.000000 +2102.879883 14.320000 2.380000 1.850000 184.000000 +2112.830078 14.340000 2.380000 1.850000 185.000000 +2122.780029 14.360000 2.390000 1.850000 186.000000 +2132.729980 14.380000 2.400000 1.850000 187.000000 +2142.679932 14.390000 2.400000 1.850000 188.000000 +2152.620117 14.410000 2.410000 1.850000 189.000000 +2162.560059 14.430000 2.410000 1.850000 190.000000 +2172.500000 14.440000 2.420000 1.850000 191.000000 +2182.429932 14.460000 2.430000 1.850000 192.000000 +2192.370117 14.470000 2.430000 1.850000 193.000000 +2202.300049 14.490000 2.440000 1.850000 194.000000 +2212.219971 14.500000 2.440000 1.850000 195.000000 +2222.149902 14.510000 2.450000 1.850000 196.000000 +2232.070068 14.520000 2.460000 1.850000 197.000000 +2241.989990 14.530000 2.460000 1.850000 198.000000 +2251.899902 14.540000 2.470000 1.850000 199.000000 +2261.820068 14.550000 2.470000 1.850000 200.000000 +2271.729980 14.560000 2.480000 1.850000 201.000000 +2281.639893 14.570000 2.490000 1.850000 202.000000 +2291.540039 14.580000 2.490000 1.850000 203.000000 +2301.449951 14.580000 2.500000 1.850000 204.000000 +2311.350098 14.590000 2.500000 1.850000 205.000000 +2321.250000 14.600000 2.510000 1.850000 206.000000 +2331.139893 14.600000 2.520000 1.850000 207.000000 +2341.030029 14.610000 2.520000 1.850000 208.000000 +2350.919922 14.610000 2.530000 1.850000 209.000000 +2360.810059 14.610000 2.530000 1.850000 210.000000 +2370.699951 14.610000 2.540000 1.850000 211.000000 +2380.580078 14.610000 2.550000 1.850000 212.000000 +2390.459961 14.620000 2.550000 1.850000 213.000000 +2400.330078 14.620000 2.560000 1.850000 214.000000 +2410.209961 14.620000 2.560000 1.850000 215.000000 +2420.080078 14.610000 2.570000 1.850000 216.000000 +2429.949951 14.610000 2.580000 1.850000 217.000000 +2439.810059 14.610000 2.580000 1.850000 218.000000 +2449.679932 14.610000 2.590000 1.850000 219.000000 +2459.540039 14.600000 2.590000 1.850000 220.000000 +2469.399902 14.600000 2.600000 1.850000 221.000000 +2479.250000 14.590000 2.610000 1.850000 222.000000 +2489.110107 14.590000 2.610000 1.850000 223.000000 +2498.959961 14.580000 2.620000 1.850000 224.000000 diff --git a/_downloads/c39288d9d7f253777a2c4ff87f458cc7/92AV3C.lan b/_downloads/c39288d9d7f253777a2c4ff87f458cc7/92AV3C.lan new file mode 100644 index 0000000..fc0eac7 Binary files /dev/null and b/_downloads/c39288d9d7f253777a2c4ff87f458cc7/92AV3C.lan differ diff --git a/_images/covariance.png b/_images/covariance.png new file mode 100644 index 0000000..4757884 Binary files /dev/null and b/_images/covariance.png differ diff --git a/_images/fld3.png b/_images/fld3.png new file mode 100644 index 0000000..ce6d5a6 Binary files /dev/null and b/_images/fld3.png differ diff --git a/_images/fld_training.png b/_images/fld_training.png new file mode 100644 index 0000000..6ed88f9 Binary files /dev/null and b/_images/fld_training.png differ diff --git a/_images/fld_training_errors.png b/_images/fld_training_errors.png new file mode 100644 index 0000000..931645e Binary files /dev/null and b/_images/fld_training_errors.png differ diff --git a/_images/gmlc2_errors.png b/_images/gmlc2_errors.png new file mode 100644 index 0000000..e1c9a49 Binary files /dev/null and b/_images/gmlc2_errors.png differ diff --git a/_images/gmlc2_training.png b/_images/gmlc2_training.png new file mode 100644 index 0000000..7c9c4aa Binary files /dev/null and b/_images/gmlc2_training.png differ diff --git a/_images/gmlc_errors.png b/_images/gmlc_errors.png new file mode 100644 index 0000000..306351e Binary files /dev/null and b/_images/gmlc_errors.png differ diff --git a/_images/gmlc_map.png b/_images/gmlc_map.png new file mode 100644 index 0000000..5c2d8bd Binary files /dev/null and b/_images/gmlc_map.png differ diff --git a/_images/gmlc_map_training.png b/_images/gmlc_map_training.png new file mode 100644 index 0000000..2592ef6 Binary files /dev/null and b/_images/gmlc_map_training.png differ diff --git a/_images/hypercube.jpg b/_images/hypercube.jpg new file mode 100644 index 0000000..d11a2fc Binary files /dev/null and b/_images/hypercube.jpg differ diff --git a/_images/hypercube_big.jpg b/_images/hypercube_big.jpg new file mode 100644 index 0000000..5b6aefc Binary files /dev/null and b/_images/hypercube_big.jpg differ diff --git a/_images/imshow_92AV3C_gt.png b/_images/imshow_92AV3C_gt.png new file mode 100644 index 0000000..4550850 Binary files /dev/null and b/_images/imshow_92AV3C_gt.png differ diff --git a/_images/imshow_92AV3C_overlay.png b/_images/imshow_92AV3C_overlay.png new file mode 100644 index 0000000..b584c36 Binary files /dev/null and b/_images/imshow_92AV3C_overlay.png differ diff --git a/_images/imshow_92AV3C_rgb.png b/_images/imshow_92AV3C_rgb.png new file mode 100644 index 0000000..e122932 Binary files /dev/null and b/_images/imshow_92AV3C_rgb.png differ diff --git a/_images/kmeans_20_30.jpg b/_images/kmeans_20_30.jpg new file mode 100644 index 0000000..f6841e0 Binary files /dev/null and b/_images/kmeans_20_30.jpg differ diff --git a/_images/kmeans_20_30_centers.png b/_images/kmeans_20_30_centers.png new file mode 100644 index 0000000..ece3f07 Binary files /dev/null and b/_images/kmeans_20_30_centers.png differ diff --git a/_images/limestone.png b/_images/limestone.png new file mode 100644 index 0000000..c621127 Binary files /dev/null and b/_images/limestone.png differ diff --git a/_images/math/08d5b0516b4375a7755ccd45c82be74cbf9a46e7.png b/_images/math/08d5b0516b4375a7755ccd45c82be74cbf9a46e7.png new file mode 100644 index 0000000..46eb58d Binary files /dev/null and b/_images/math/08d5b0516b4375a7755ccd45c82be74cbf9a46e7.png differ diff --git a/_images/math/20c9c2a2779214a9e4366f71e6058b877f7a2857.png b/_images/math/20c9c2a2779214a9e4366f71e6058b877f7a2857.png new file mode 100644 index 0000000..a0dfb27 Binary files /dev/null and b/_images/math/20c9c2a2779214a9e4366f71e6058b877f7a2857.png differ diff --git a/_images/math/21e801746864c76b57574f960ac4edd3ba626c8b.png b/_images/math/21e801746864c76b57574f960ac4edd3ba626c8b.png new file mode 100644 index 0000000..7ca03ed Binary files /dev/null and b/_images/math/21e801746864c76b57574f960ac4edd3ba626c8b.png differ diff --git a/_images/math/2337d04a5b957d3fdc3ad00fa0fac26f38e8602c.png b/_images/math/2337d04a5b957d3fdc3ad00fa0fac26f38e8602c.png new file mode 100644 index 0000000..28ba1e0 Binary files /dev/null and b/_images/math/2337d04a5b957d3fdc3ad00fa0fac26f38e8602c.png differ diff --git a/_images/math/3c2d93b6dfc6a208fa63f256b941b68c902b5528.png b/_images/math/3c2d93b6dfc6a208fa63f256b941b68c902b5528.png new file mode 100644 index 0000000..4802cf7 Binary files /dev/null and b/_images/math/3c2d93b6dfc6a208fa63f256b941b68c902b5528.png differ diff --git a/_images/math/4f3bdafd2863b618a470035befb4daeef90f1d97.png b/_images/math/4f3bdafd2863b618a470035befb4daeef90f1d97.png new file mode 100644 index 0000000..887a2ec Binary files /dev/null and b/_images/math/4f3bdafd2863b618a470035befb4daeef90f1d97.png differ diff --git a/_images/math/50b6f08425d9c4974e96bcde8c07b8dfbf271cb8.png b/_images/math/50b6f08425d9c4974e96bcde8c07b8dfbf271cb8.png new file mode 100644 index 0000000..f682e86 Binary files /dev/null and b/_images/math/50b6f08425d9c4974e96bcde8c07b8dfbf271cb8.png differ diff --git a/_images/math/51c142e155733b3efe0c5fe4628975cda12a22df.png b/_images/math/51c142e155733b3efe0c5fe4628975cda12a22df.png new file mode 100644 index 0000000..89ca8d8 Binary files /dev/null and b/_images/math/51c142e155733b3efe0c5fe4628975cda12a22df.png differ diff --git a/_images/math/59c6879cd38c227825731a682a925c733deb5ba4.png b/_images/math/59c6879cd38c227825731a682a925c733deb5ba4.png new file mode 100644 index 0000000..f9b3aaa Binary files /dev/null and b/_images/math/59c6879cd38c227825731a682a925c733deb5ba4.png differ diff --git a/_images/math/638083c0831486fca4c054b988560722cbe53754.png b/_images/math/638083c0831486fca4c054b988560722cbe53754.png new file mode 100644 index 0000000..3f71756 Binary files /dev/null and b/_images/math/638083c0831486fca4c054b988560722cbe53754.png differ diff --git a/_images/math/6afb20d86b0ace64a91f3099474b5ec02aacefc1.png b/_images/math/6afb20d86b0ace64a91f3099474b5ec02aacefc1.png new file mode 100644 index 0000000..4502a47 Binary files /dev/null and b/_images/math/6afb20d86b0ace64a91f3099474b5ec02aacefc1.png differ diff --git a/_images/math/6edc5c119344e25a06e6ac4cb56f2d5e2f09a2f1.png b/_images/math/6edc5c119344e25a06e6ac4cb56f2d5e2f09a2f1.png new file mode 100644 index 0000000..380800e Binary files /dev/null and b/_images/math/6edc5c119344e25a06e6ac4cb56f2d5e2f09a2f1.png differ diff --git a/_images/math/77da5b6e9e7f4c7eb4b406187c7db48570402ca7.png b/_images/math/77da5b6e9e7f4c7eb4b406187c7db48570402ca7.png new file mode 100644 index 0000000..c777d5a Binary files /dev/null and b/_images/math/77da5b6e9e7f4c7eb4b406187c7db48570402ca7.png differ diff --git a/_images/math/85ea06190cc6e4a593bdc4c087ae7990ab30000e.png b/_images/math/85ea06190cc6e4a593bdc4c087ae7990ab30000e.png new file mode 100644 index 0000000..69a77e6 Binary files /dev/null and b/_images/math/85ea06190cc6e4a593bdc4c087ae7990ab30000e.png differ diff --git a/_images/math/86b7855117979b12ae23c41842e728fa7dc3357c.png b/_images/math/86b7855117979b12ae23c41842e728fa7dc3357c.png new file mode 100644 index 0000000..cfcc586 Binary files /dev/null and b/_images/math/86b7855117979b12ae23c41842e728fa7dc3357c.png differ diff --git a/_images/math/888f7c323ac0341871e867220ae2d76467d74d6e.png b/_images/math/888f7c323ac0341871e867220ae2d76467d74d6e.png new file mode 100644 index 0000000..2047e47 Binary files /dev/null and b/_images/math/888f7c323ac0341871e867220ae2d76467d74d6e.png differ diff --git a/_images/math/8af4d257dda3c8c78d5150150a5b87711974d8db.png b/_images/math/8af4d257dda3c8c78d5150150a5b87711974d8db.png new file mode 100644 index 0000000..fd81a2c Binary files /dev/null and b/_images/math/8af4d257dda3c8c78d5150150a5b87711974d8db.png differ diff --git a/_images/math/8ef1f282527041d7d5c4a46dd2c60a02a7a7c00b.png b/_images/math/8ef1f282527041d7d5c4a46dd2c60a02a7a7c00b.png new file mode 100644 index 0000000..c4f1ce1 Binary files /dev/null and b/_images/math/8ef1f282527041d7d5c4a46dd2c60a02a7a7c00b.png differ diff --git a/_images/math/a3565136d9107b3df64b08768fe13a9bd5a8d79f.png b/_images/math/a3565136d9107b3df64b08768fe13a9bd5a8d79f.png new file mode 100644 index 0000000..6a62388 Binary files /dev/null and b/_images/math/a3565136d9107b3df64b08768fe13a9bd5a8d79f.png differ diff --git a/_images/math/bba4dc6f1475c69c3aee7dfaf608317264ba6d48.png b/_images/math/bba4dc6f1475c69c3aee7dfaf608317264ba6d48.png new file mode 100644 index 0000000..803ede1 Binary files /dev/null and b/_images/math/bba4dc6f1475c69c3aee7dfaf608317264ba6d48.png differ diff --git a/_images/math/c520600fcfaddf5b2eb2e6f1c20df0d37e665886.png b/_images/math/c520600fcfaddf5b2eb2e6f1c20df0d37e665886.png new file mode 100644 index 0000000..3d79554 Binary files /dev/null and b/_images/math/c520600fcfaddf5b2eb2e6f1c20df0d37e665886.png differ diff --git a/_images/math/cef212a0d6d030d89e5a153f271db0d6bb0a3c6f.png b/_images/math/cef212a0d6d030d89e5a153f271db0d6bb0a3c6f.png new file mode 100644 index 0000000..f7045f3 Binary files /dev/null and b/_images/math/cef212a0d6d030d89e5a153f271db0d6bb0a3c6f.png differ diff --git a/_images/math/f245781aa993881751610a53d8b3be5c0671325d.png b/_images/math/f245781aa993881751610a53d8b3be5c0671325d.png new file mode 100644 index 0000000..dab2556 Binary files /dev/null and b/_images/math/f245781aa993881751610a53d8b3be5c0671325d.png differ diff --git a/_images/mf_gt_02.png b/_images/mf_gt_02.png new file mode 100644 index 0000000..7ced1b2 Binary files /dev/null and b/_images/mf_gt_02.png differ diff --git a/_images/ndvi.png b/_images/ndvi.png new file mode 100644 index 0000000..f1ac1ae Binary files /dev/null and b/_images/ndvi.png differ diff --git a/_images/ndwindow_mod.png b/_images/ndwindow_mod.png new file mode 100644 index 0000000..f238de3 Binary files /dev/null and b/_images/ndwindow_mod.png differ diff --git a/_images/ndwindow_orig.png b/_images/ndwindow_orig.png new file mode 100644 index 0000000..69c83f7 Binary files /dev/null and b/_images/ndwindow_orig.png differ diff --git a/_images/pc3.png b/_images/pc3.png new file mode 100644 index 0000000..bd466ed Binary files /dev/null and b/_images/pc3.png differ diff --git a/_images/rgb_double_click.png b/_images/rgb_double_click.png new file mode 100644 index 0000000..d0f6333 Binary files /dev/null and b/_images/rgb_double_click.png differ diff --git a/_images/rgb_with_bands_double_click.png b/_images/rgb_with_bands_double_click.png new file mode 100644 index 0000000..71375be Binary files /dev/null and b/_images/rgb_with_bands_double_click.png differ diff --git a/_images/rxhistogram.png b/_images/rxhistogram.png new file mode 100644 index 0000000..6cd9ab3 Binary files /dev/null and b/_images/rxhistogram.png differ diff --git a/_images/rxvals.png b/_images/rxvals.png new file mode 100644 index 0000000..b41881c Binary files /dev/null and b/_images/rxvals.png differ diff --git a/_images/rxvals_stretched.png b/_images/rxvals_stretched.png new file mode 100644 index 0000000..002fdc0 Binary files /dev/null and b/_images/rxvals_stretched.png differ diff --git a/_images/rxvals_threshold.png b/_images/rxvals_threshold.png new file mode 100644 index 0000000..750b472 Binary files /dev/null and b/_images/rxvals_threshold.png differ diff --git a/_images/sam.png b/_images/sam.png new file mode 100644 index 0000000..b5f4233 Binary files /dev/null and b/_images/sam.png differ diff --git a/_images/spectrum_resampled.png b/_images/spectrum_resampled.png new file mode 100644 index 0000000..20cfd75 Binary files /dev/null and b/_images/spectrum_resampled.png differ diff --git a/_images/view_gt.png b/_images/view_gt.png new file mode 100644 index 0000000..4550850 Binary files /dev/null and b/_images/view_gt.png differ diff --git a/_sources/algorithms.rst.txt b/_sources/algorithms.rst.txt new file mode 100644 index 0000000..dec4410 --- /dev/null +++ b/_sources/algorithms.rst.txt @@ -0,0 +1,679 @@ + +.. toctree:: + :maxdepth: 2 + +.. _algorithms: + +=================== +Spectral Algorithms +=================== + +SPy implements various algorithms for dimensionality reduction and supervised & +unsupervised classification. Some of these algorithms are computationally +burdensome and require iterative access to image data. These algorithms will +almost always execute significantly faster if the image data is loaded into +memory. Therefore, if you use these algorithms and have sufficient computer +memory, you should load your image into memory. + +.. ipython:: + + In [3]: from spectral import * + + In [4]: img = open_image('92AV3C.lan').load() + + @suppress + In [5]: settings.show_progress = False + + +Unsupervised Classification +=========================== + +Unsupervised classification algorithms divide image pixels into groups based on +spectral similarity of the pixels without using any prior knowledge of the +spectral classes. + +k-means Clustering +------------------ + +The *k-means* algorithm takes an iterative approach to generating clusters. +The parameter *k* specifies the desired number of clusters to generate. The algorithm +begins with an initial set of cluster centers (e.g., results from :func:`~spectral.cluster`). +Each pixel in the image is then assigned to the nearest cluster center (using +distance in *N*-space as the distance metric) and each cluster center is then recomputed +as the centroid of all pixels assigned to the cluster. This process repeats until +a desired stopping criterion is reached (e.g., max number of iterations). To run +the *k-means* algorithm on the image and create 20 clusters, using a maximum of +50 iterations, call :func:`~spectral.kmeans` as follows. + +.. ipython:: + :verbatim: + + In [8]: (m, c) = kmeans(img, 20, 30) + Initializing clusters along diagonal of N-dimensional bounding box. + Starting iterations. + Iteration 1...done + 21024 pixels reassigned. + Iteration 2...done + 11214 pixels reassigned. + Iteration 3...done + 4726 pixels reassigned. + ---// snip //--- + Iteration 28...done + 248 pixels reassigned. + Iteration 29...done + 215 pixels reassigned. + Iteration 30...done + 241 pixels reassigned. + ^CIteration 31... 15.9%KeyboardInterrupt: Returning clusters from previous iteration + +Note that I interrupted the algorithm with a keyboard interrupt (CTRL-C) after +the 30th iteration since there were only about a hundred pixels migrating between +clusters at that point. :func:`~spectral.kmeans` catches the :exc:`KeyboardInterrupt` +exception and returns the clusters generated at the end of the previous +iteration. If you are running the algorithm interactively, this feature allows +you to set the max number of iterations to an arbitrarily high number and then +stop the algorithm when the clusters have converged to an acceptable level. If you +happen to set the max number of iterations too small (many pixels are still migrating +at the end of the final iteration), you can simply call :func:`~spectral.kmeans` again +to resume processing by passing the cluster centers generated by the previous call +as the optional :class:`start_clusters` argument to the function. + +.. figure:: images/kmeans_20_30.jpg + :align: center + :scale: 120 % + + k-means clustering results + +Notice that :func:`~spectral.kmeans` appears to have captured much more of the +spectral variation in the image than the single-pass :func:`~spectral.cluster` +function. Let's also take a look at the the cluster centers produced by the +algorithm: + +.. ipython:: python + :verbatim: + + import matplotlib.pyplot as plt + plt.figure() + + for i in range(c.shape[0]): + plt.plot(c[i]) + plt.grid() + +.. figure:: images/kmeans_20_30_centers.png + :align: center + :scale: 100 % + + k-means cluster centers + + +Supervised Classification +=========================== + +Training Data +------------- + +Performing supervised classification requires training a classifier with training +data that associates samples with particular training classes. To assign class +labels to pixels in an image having *M* rows and *N* columns, you must provide an +*MxN* integer-valued ground truth array whose elements are indices for the corresponding +training classes. A value of 0 in the ground truth array indicate an unlabeled pixel +(the pixel is not associated with a training class). + +The following commands will load and display the ground truth map for our sample +image: + +.. ipython:: + + In [14]: gt = open_image('92AV3GT.GIS').read_band(0) + + @savefig view_gt.png scale=75% align=center + In [15]: v = imshow(classes=gt) + +We can now create a :class:`~spectral.TrainingClassSet` object by calling +:func:`~spectral.create_training_classes`: + +.. ipython:: + + In [15]: classes = create_training_classes(img, gt) + + +With the training classes defined, we can then create a supervised classifier, +train it with the training classes, and then classify an image. + +Gaussian Maximum Likelihood Classification +------------------------------------------ + +In this case, we'll perform Gaussian Maximum Likelihood Classification (GMLC), +so let's create the appropriate classifier. + +.. ipython:: + + In [16]: gmlc = GaussianClassifier(classes) + +When we created the classifier, it was automatically trained on the training sets +we provided. Notice that the classifier ignored five of the training classes. +GMLC requires computing the inverse of the covariance matrix for each training +class. Since our sample image contains 220 spectral bands, classes with fewer +than 220 samples will have singular covariance matrices, for which we can't compute +the inverse. + +Once the classifier is trained, we can use it to classify an image having +the same spectral bands as the training set. Let's classify our training image +and display the resulting classification map. + +.. ipython:: + + In [17]: clmap = gmlc.classify_image(img) + Classifying image...done + + @savefig gmlc_map.png scale=75% align=center + In [18]: v = imshow(classes=clmap) + +The classification map above shows classification results for the entire image. To view +results for only the ground truth pixels we must mask out all the pixels not +associated with a training class. + +.. ipython:: + + In [19]: gtresults = clmap * (gt != 0) + + @savefig gmlc_map_training.png scale=75% align=center + In [20]: v = imshow(classes=gtresults) + +If the classification results are good, we expect the classification map +above to look very similar to the original ground truth map. To view only the +errors, we must mask out all elements in :obj:`gtResults` that do not match the +ground truth image. + +.. ipython:: + + In [22]: gterrors = gtresults * (gtresults != gt) + + @savefig gmlc_errors.png scale=75% align=center + In [20]: v = imshow(classes=gterrors) + +The five contiguous regions in the error image above correspond to the ground +truth classes that the :class:`~spectral.GaussianClassifier` ignored because they +had too few samples. + +The following table lists the supervised classifiers SPy currently provides. + +.. table:: + + ======================================================================= ========================================================== + Classifier Description + ======================================================================= ========================================================== + :class:`~spectral.algorithms.classifiers.GaussianClassifier` Gaussian Maximum Likelihood + :class:`~spectral.algorithms.classifiers.MahalanobisDistanceClassifier` Mahalanobis Distance + :class:`~spectral.algorithms.perceptron.PerceptronClassifier` Multi-Layer Perceptron + ======================================================================= ========================================================== + +.. dimensionality-reduction: + +Dimensionality Reduction +=========================== + +Processing hyperspectral images with hundreds of bands can be computationally +burdensome and classification accuracy may suffer due to the so-called "curse +of dimensionality". To mitigate these problems, it is often desirable to reduce +the dimensionality of the data. + +Principal Components +-------------------- + +Many of the bands within hyperspectral images are often strongly correlated. +The principal components transformation represents a linear transformation of the +original image bands to a set of new, uncorrelated features. These new features +correspond to the eigenvectors of the image covariance matrix, where the associated +eigenvalue represents the variance in the direction of the eigenvector. A very +large percentage of the image variance can be captured in a relatively small +number of principal components (compared to the original number of bands) . + +The SPy function :func:`~spectral.principal_components` computes the principal +components of the image data and returns the mean, covariance, eigenvalues, and +eigenvectors in a :class:`~spectral.algorithms.algorithims.PrincipalComponents`. +This object also contains a transform to rotate data in to the space of the +principal compenents, as well as a method to reduce the number of eigenvectors. + +.. ipython:: + + In [60]: pc = principal_components(img) + Covariance.....done + + @savefig covariance.png scale=75% align=center + In [61]: v = imshow(pc.cov) + +In the covariance matrix display, whiter values indicate strong positive covariance, +darker values indicate strong negative covariance, and grey values indicate +covariance near zero. + +To reduce dimensionality using principal components, we can sort the eigenvalues +in descending order and then retain enough eigenvalues (an corresponding eigenvectors) +to capture a desired fraction of the total image variance. We then reduce the +dimensionality of the image pixels by projecting them onto the remaining eigenvectors. +We will choose to retain a minimum of 99.9% of the total image variance. + +.. ipython:: + + In [62]: pc_0999 = pc.reduce(fraction=0.999) + + In [62]: # How many eigenvalues are left? + + In [63]: len(pc_0999.eigenvalues) + + In [64]: img_pc = pc_0999.transform(img) + + @savefig pc3.png scale=75% align=center + In [65]: v = imshow(img_pc[:,:,:3], stretch_all=True) + +Now we'll use a Gaussian maximum likelihood classifier (GMLC) for the reduced +principal components to train and classify against the training data. + +.. ipython:: + + In [68]: classes = create_training_classes(img_pc, gt) + + In [68]: gmlc = GaussianClassifier(classes) + + In [69]: clmap = gmlc.classify_image(img_pc) + + In [70]: clmap_training = clmap * (gt != 0) + + @savefig gmlc2_training.png scale=75% align=center + In [71]: v = imshow(classes=clmap_training) + +And the associated errors: + +.. ipython:: + + In [72]: training_errors = clmap_training * (clmap_training != gt) + + @savefig gmlc2_errors.png scale=75% align=center + In [73]: v = imshow(classes=training_errors) + + +Fisher Linear Discriminant +--------------------------- + +The Fisher Linear Discriminant (a.k.a., canonical discriminant) attempts to +find a set of transformed axes that maximize the ratio of the average distance +between classes to the average distance between samples within each class. [Richards1999]_ +This is written as + +.. math:: + + C_b x = \lambda C_w x + +where :math:`C_b` is the covariance of the class centers and :math:`C_w` is the +weighted average of the covariances of each class. If :math:`C_w` is invertible, +this becomes + +.. math:: + + \left(C_{w}^{-1} C_b\right)x=\lambda x + +This eigenvalue problem is solved by the :func:`~spectral.linear_discriminant` +function, yielding `C-1` eigenvalues, where `C` is the number of classes. + +.. ipython:: + + In [74]: classes = create_training_classes(img, gt) + + In [75]: fld = linear_discriminant(classes) + + In [76]: len(fld.eigenvectors) + +Let's view the image projected onto the top 3 components of the transform: + +.. ipython:: + + In [77]: img_fld = fld.transform(img) + + @savefig fld3.png scale=75% align=center + In [79]: v = imshow(img_fld[:, :, :3]) + +Next, we'll classify the data using this discriminant. + +.. ipython:: + + In [80]: classes.transform(fld.transform) + + In [81]: gmlc = GaussianClassifier(classes) + + In [82]: clmap = gmlc.classify_image(img_fld) + + In [83]: clmap_training = clmap * (gt != 0) + + @savefig fld_training.png scale=75% align=center + In [84]: v = imshow(classes=clmap_training) + +.. ipython:: + + In [85]: fld_errors = clmap_training * (clmap_training != gt) + + @savefig fld_training_errors.png scale=75% align=center + In [87]: v = imshow(classes=fld_errors) + +.. seealso:: + + :func:`~spectral.algorithms.algorithms.orthogonalize`: + + Gram-Schmidt Orthogonalization + +Target Detectors +================ + +RX Anomaly Detector +------------------- + +The RX anomaly detector uses the squared Mahalanobis distance as a measure of +how anomalous a pixel is with respect to an assumed background. The SPy +:func:`~spectral.algorithms.detectors.rx` function computes RX scores for an +array of image pixels. The squared Mahalanobis distance is given by + +.. math:: + + y=(x-\mu_b)^T\Sigma_b^{-1}(x-\mu_b) + +where :math:`x` is the pixel spectrum, :math:`\mu_b` is the background +mean, and :math:`\Sigma_b` is the background covariance [Reed_Yu_1990]_. + + +If no background statistics are passed to the :func:`rx` function, +background statistics will be estimated from the array of pixels for which the +RX scores are to be calculated. + +.. ipython:: + + In [1]: rxvals = rx(img) + +To declare pixels as anomalous, we need to specify a threshold RX score. For +example, we could choose all image pixels whose RX score has a probability of +less than 0.001 with respect to the background: + +.. ipython:: + + In [1]: from scipy.stats import chi2 + + In [1]: nbands = img.shape[-1] + + In [1]: P = chi2.ppf(0.999, nbands) + + @savefig rxvals_threshold.png scale=75% align=center + In [1]: v = imshow(1 * (rxvals > P)) + + +Rather than specifying a threshold for anomalous pixels, one can also simply +view an image of raw RX scores, where brighter pixels are considered "more anomalous": + +.. ipython:: + + @savefig rxvals.png scale=75% align=center + In [1]: v = imshow(rxvals) + +For the sample image, only a few pixels are visible in the image of RX scores +because a linear color scale is used and there is a very small number of pixels with RX +scores much higher than other pixels. This is apparent from viewing the histogram +of the RX scores. + +.. ipython:: + + In [1]: import matplotlib.pyplot as plt + + In [1]: f = plt.figure() + + In [1]: h = plt.hist(rxvals.ravel(), 200, log=True) + + @savefig rxhistogram.png scale=75% align=center + In [1]: h = plt.grid() + +The outliers are not obvious in the histogram, so let's print their values: + +.. ipython:: + + In [1]: print(np.sort(rxvals.ravel())[-10:]) + + +To view greater detail in the RX image, we can adjust the lower and upper limits +of the image display. Since we are primarily interested in the most anomalous +pixels, we will set the black level to the 99th percentile of the RX values' +cumulative histogram and set the white point to the 99.99th percentile: + +.. ipython:: + + @savefig rxvals_stretched.png scale=75% align=center + In [1]: v = imshow(rxvals, stretch=(0.99, 0.9999)) + +We can see the new RGB data limits by inspecting the returned +:class:`~spectral.graphics.spypylab.ImageView` object: + +.. ipython:: + + In [1]: print(v) + +Note that we could also have set the contrast stretch to explicit RX values +(vice percentile values) by specifying the *bounds* keyword instead of *stretch*. + +If an image contains regions with different background materials, then the +assumption of a single mean/covariance for background pixels can reduce +performance of the RX anomaly detector. In such situations, better results +can be obtained by dynamically computing background statistics in a neighborhood +around each pixel being evaluated. + +To compute local background statistics for +each pixel, the :func:`rx` function accepts an optional `window` argument, which +specifies an inner/outer window within which to calculate background statistics +for each pixel being evaluated. The outer window is the window within which +background statistics will be calculated. The inner window is a smaller window +(within the outer window) indicating an exclusion zone within which pixels are +to be ignored. The purpose of the inner window is to prevent potential anomaly/target +pixels from "polluting" background statistics. + +For example, to compute RX scores with background statistics computed from a +21 :math:`\times` 21 pixel window about the pixel being evaluated, with an exclusion window of +5 :math:`\times` 5 pixels, the function would be called as follows: + +.. ipython:: + + @verbatim + In [1]: rxvals = rx(img, window=(5,21)) + +While the use of a windowed background will often improve results for images containing +multiple background materials, it does so at the expense of introducing two +issues. First, the sizes of the inner and outer windows must be specified such +that the resulting covariance has full rank. That is, if :math:`w_{in}` and +:math:`w_{out}` represent the pixel widths of the inner and outer windows, respectively, +then :math:`w_{out}^2 - w_{in}^2` must be at least as large as the number of +image bands. Second, recomputing the estimated background covariance for each +pixel in the image makes the computational complexity of of the RX score +computation orders of magnitue greater. + +As a compromise between a fixed background and recomputation of mean & covariance +for each pixel, :func:`rx` can be passed a global covariance estimate in addition +to the *window* argument. In this case, only the background mean within the window will +be recomputed for each pixel. This significanly reduces computation time +for the windowed algorithm and removes the size limitation on the window (except +that the outer window must be larger than the inner). For example, since our sample image has ground +cover classes labeled, we can compute the average covariance over those ground +cover classes and use the result as an estimate of the global covariance. + +.. ipython:: + + @verbatim + In [1]: C = cov_avg(img, gt) + + @verbatim + In [1]: rxvals = rx(img, window=(5,21), cov=C) + +Matched Filter +-------------- + +The matched filter is a linear detector given by the formula + +.. math:: + + y=\frac{(\mu_t-\mu_b)^T\Sigma_b^{-1}(x-\mu_b)}{(\mu_t-\mu_b)^T\Sigma^{-1}(\mu_t-\mu_b)} + +where :math:`\mu_t` is the target mean, :math:`\mu_b` is the background +mean, and :math:`\Sigma_b` is the covariance. The matched filter response is +scaled such that the response is zero when the input is equal to the background +mean and equal to one when the pixel is equal to the target mean. Like the +:func:`~spectral.algorithms.detectors.rx` function the SPy +:func:`~spectral.algorithms.detectors.matched_filter` function will estimate +background statistics from the input image if no background statistics are +specified. + +Let's select the image pixel at (row, col) = (8, 88) as our target, use a +global background statistics estimate, and plot all pixels whose matched filter +scores are greater than 0.2. + +.. ipython:: + + In [1]: t = img[8, 88] + + In [1]: mfscores = matched_filter(img, t) + + @savefig mf_gt_02.png scale=75% align=center + In [1]: v = imshow(1 * (mfscores > 0.2)) + +As with the :func:`rx` function, :func:`matched_filter` can be applied using +windowed background statistics (optionally with a global covariance estimate). + +.. seealso:: + + :func:`~spectral.algorithms.detectors.ace`: + + Adaptive Coherence/Cosine Estimator (ACE) + +Miscellaneous Functions +======================= + +Band Resampling +--------------- + +Comparing spectra measured with a particular sensor to spectra collected by a +different sensor often requires resampling spectra to a common band discretization. +Spectral bands of a single sensor may drift enough over time such that spectra +collected by the same sensor at different dates requires resampling. + +For resampling purposes, SPy treats a sensor as having Gaussian spectral response +functions for each of its spectral bands. A source sensor band will contribute +to any destination band where there is overlap between the FWHM of the response +functions of the two bands. If there is an overlap, an integral is +performed over the region of overlap assuming the source band data value is +constant over its FWHM (since we do not know the true spectral load over the +source band) and the destination band has a Gaussian response function. Any +target bands that do not have an overlapping source band will produce :const:`NaN` +as the resampled band value. If FWHM information is not available for a sensor's +bands, each band's FWHM is assumed to reach half the distance the its adjacent +bands. Resampling results are better when source bands are at a higher spectral +resolution than the destination bands. + +To create BandResampler object, we can either pass it a +:class:`~spectral.algorithms.resampling.BandResampler` object for each sensor or +a list of band centers and, optionally, a list of FWHM values. Once the +BandResampler is created, we can call it with a source sensor spectrum and it will +return the resampled spectrum. + +.. ipython:: python + :verbatim: + + import spectral.io.aviris as aviris + img1 = aviris.open('f970619t01p02_r02_sc04.a.rfl', 'f970619t01p02_r02.a.spc') + bands2 = aviris.read_aviris_bands('92AV3C.spc') + resample = BandResampler(img1.bands, bands2) + x1 = img1[96, 304] + x2 = resample(x1) + +NDVI +---- + +The Normalized Difference Vegetation Index (NDVI) is an indicator of the presence +of vegetation. The index is commonly defined as + +.. math:: + + NDVI = \frac{NIR-RED}{NIR+RED} + +where `NIR` is the reflectance in the near infrared (NIR) part of the spectrum and +`RED` is the reflectance of the red band. For our sample image, if we take +band 21 (607.0 nm) for our red band and band 43 (802.5 nm) for near infrared, +we get the following NDVI image. + +.. ipython:: + + In [103]: vi = ndvi(img, 21, 43) + + @savefig ndvi.png scale=75% align=center + In [104]: v = imshow(vi) + +:func:`~spectral.algorithms.algorithms.ndvi` is a simple convenience function. +You could just as easily calculate the vegetation index yourself like this: + +.. ipython:: + + In [88]: red = img.read_band(21) + + In [89]: nir = img.read_band(43) + + In [90]: vi = (nir - red) / (nir + red) + +Spectral Angles +--------------- + +A spectral angle refers to the angle between to spectra in N-space. In the +absence of covariance data, spectral angles can be used for classifying data +against a set of reference spectra by selecting the reference spectrum with +which the unknown spectrum has the smallest angle. + +To classify our sample image to the ground truth classes using spectral angles, +we must compute the spectral angles for each pixel with each training class mean. +This is done by the :func:`~spectral.algorithms.algorithms.spectral_angles` function. +Before calling the function, we must first create a `CxB` array of training class +mean spectra, where `C` is the number of training classes and `B` is the number +of spectral bands. + +.. ipython:: + + In [96]: import numpy as np + + In [97]: classes = create_training_classes(img, gt, True) + Covariance.....done + Covariance.....done + ---// snip //--- + Covariance.....done + + In [98]: means = np.zeros((len(classes), img.shape[2]), float) + + In [99]: for (i, c) in enumerate(classes): + ....: means[i] = c.stats.mean + ....: + +In the code above, the :const:`True` parameter to +:func:`~spectral.create_training_classes` forces calculation +of statistics for each class. Next, we call :func:`~spectral.algorithms.algorithms.spectral_angles`, +which returns an `MxNxC` array, where `M` and `N` are the number of rows and +columns in the image and there are `C` spectral angle for each pixel. To select +the class with the smallest angle, we call the :mod:`numpy` :func:`~numpy.argmin` +function to select the index for the smallest angle corresponding to each pixel. +The ``clmap + 1`` is used in the display command because our class IDs start at 1 (not 0). + +.. ipython:: + + In [100]: angles = spectral_angles(img, means) + + In [101]: clmap = np.argmin(angles, 2) + + @savefig sam.png scale=75% align=center + In [102]: v = imshow(classes=((clmap + 1) * (gt != 0))) + +.. seealso:: + + :func:`~spectral.algorithms.algorithms.msam`: Modified Spectral Angle Mapper + +.. rubric:: References + +.. [Richards1999] Richards, J.A. & Jia, X. Remote Sensing Digital Image Analysis: An Introduction. (Springer: Berlin, 1999). + +.. [Reed_Yu_1990] Reed, I.S. and Yu, X., "Adaptive multiple-band CFAR detection of an optical pattern with unknown spectral distribution," IEEE Trans. Acoust., Speech, Signal Processing, vol. 38, pp. 1760-1770, Oct. 1990. + diff --git a/_sources/class_func_glossary.rst.txt b/_sources/class_func_glossary.rst.txt new file mode 100644 index 0000000..17ef76c --- /dev/null +++ b/_sources/class_func_glossary.rst.txt @@ -0,0 +1,218 @@ +.. toctree:: + :maxdepth: 2 + +.. _class-func-glossary: + +======================== +Class/Function Glossary +======================== + +File Input/Output +================= + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :func:`~spectral.database.EcostressDatabase` + - Create/Query a spectral database generated from the ECOSTRESS Spectral Library + * - :func:`aviris.open ` + - Open an AVIRIS image file + * - :func:`aviris.read_aviris_bands ` + - Read an AVIRIS band calibration file + * - :func:`envi.open ` + - Open a data file (image, classification, or spectral library) that has + an ENVI header + * - :func:`envi.create_image ` + - Open a new (empty) image file with an ENVI header + * - :func:`envi.save_classification ` + - Save classification labels to a file with a corresponding ENVI header + * - :func:`envi.save_image ` + - Save image data to a file with a corresponding ENVI header + * - :func:`envi.SpectralLibrary ` + - Class to create/save spectral libraries in ENVI format + * - :func:`erdas.open ` + - Open an Erdas LAN image file + * - :func:`~spectral.spectral.open_image` + - Generic function for opening multiple hyperspectral image file formats + +| + +Display/Visualization +===================== + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :class:`~spectral.ColorScale` + - Create color scales for use with data display functions + * - :func:`~spectral.graphics.graphics.get_rgb` + - Produce RGB array suitable for display from image data + * - :func:`~spectral.graphics.spypylab.ImageView` + - Class for interacting with image displays (returned by :func:`~spectral.graphics.spypylab.imshow`) + * - :func:`~spectral.graphics.spypylab.imshow` + - Primary function for displaying raster views of image data (an extension of matplotlib's `imshow` providing overlays and interactivity) + * - :func:`~spectral.algorithms.algorithms.ppi` + - Display Pixel Purity Index values while :func:`ppi` function executes. + * - :func:`~spectral.save_rgb` + - Saves image data in common RGB image formats (e.g., JPEG, PNG) + * - :func:`~spectral.view_cube` + - View an interactive 3D image cube + * - :func:`~spectral.view_nd` + - Display an interactive N-D visualization of image pixel data + +| + +Dimensionality Reduction +======================== + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :func:`~spectral.linear_discriminant` + - Computes Fisher's Linear Discriminant for a set of classes + * - :func:`~spectral.algorithms.algorithms.mnf` + - Minimum Noise Fraction + * - :func:`~spectral.algorithms.algorithms.ppi` + - Calculates Pixel Purity Index (PPI) + * - :func:`~spectral.principal_components` + - Calculates principal components of an image + +| + +Target Detection +================ + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :func:`~spectral.algorithms.detectors.ace` + - Adaptive Coherence/Cosine Estimator + * - :func:`~spectral.algorithms.detectors.matched_filter` + - Applies a linear matched filter detector for a given target + * - :func:`~spectral.algorithms.algorithms.msam` + - Modified Spectral Angle Mapper (MSAM) + * - :func:`~spectral.algorithms.detectors.rx` + - RX anomaly detector + * - :func:`~spectral.algorithms.algorithms.spectral_angles` + - Spectral Angle Mapper (SAM) + +| + +Classification +============== + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :func:`~spectral.create_training_classes` + - Creates a :class:`TrainingClassSet ` object from image data and ground truth + * - :class:`~spectral.algorithms.classifiers.GaussianClassifier` + - Gaussian Maximum Likelihood Classifier (GMLC) + * - :func:`~spectral.kmeans` + - Unsupervised image classification via k-means clustering + * - :class:`~spectral.algorithms.classifiers.MahalanobisDistanceClassifier` + - A classifier using Mahalanobis distance + * - :func:`~spectral.algorithms.spatial.map_class_ids` + - Create a mapping between class labels in two classification images + * - :func:`~spectral.algorithms.spatial.map_classes` + - Modifies class indices according to a class index mapping + * - :func:`~spectral.algorithms.algorithms.msam` + - Modified Spectral Angle Mapper (MSAM) + * - :func:`~spectral.algorithms.classifiers.PerceptronClassifier` + - A Multi-Layer Perceptron (MLP) Artificial Neural Network (ANN) classifier + * - :func:`~spectral.algorithms.algorithms.spectral_angles` + - Spectral Angle Mapper (SAM) + * - :class:`~spectral.algorithms.algorithms.TrainingClassSet` + - Object returned from :func:`spectral.create_training_classes` + (used by some classifiers) + +| + +Spectral Transforms +=================== + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :class:`~spectral.BandInfo` + - Container for spectral band discretization/calibration data + * - :class:`~spectral.algorithms.resampling.BandResampler` + - Class for performing band resampling + * - :class:`FisherLinearDiscriminant ` + - Object returned by :func:`~spectral.linear_discriminant` to transform image data into linear discriminant space. + * - :class:`~spectral.algorithms.transforms.LinearTransform` + - A callable linear transform that can be applied to image data + * - :class:`MNFResult ` + - Object returned by :func:`~spectral.algorithms.algorithms.mnf` to reduce dimensionality via Minimum Noise Fraction (MNF) + * - :func:`~spectral.algorithms.algorithms.ndvi` + - Normalized Difference Vegetation Index (NDVI) + * - :class:`PrincipalComponents ` + - Object returned by :func:`~spectral.principal_components`. Transforms data into PCA space and - optionally - reduces dimensionality. + +| + +Math/Statistics +=============== + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :func:`~spectral.algorithms.algorithms.bdist` + - Bhattacharyya distance + * - :func:`~spectral.calc_stats` + - Calculates Gaussian statistics for image data + * - :func:`~spectral.algorithms.algorithms.covariance` + - Calculates image data covariance + * - :func:`~spectral.algorithms.algorithms.cov_avg` + - Calculates covariance averaged over a set of ground truth classes + * - :func:`noise_from_diffs ` + - Estimate image noise statistics from a homogeneous region + * - :func:`~spectral.algorithms.algorithms.orthogonalize` + - Performs Gram-Schmidt orthogonalization on a set of vectors + * - :func:`~spectral.principal_components` + - Calculates principal components of an image + * - :func:`~spectral.io.spyfile.transform_image` + - Applies a linear transform to image data + +Miscellaneous +============= + +.. list-table:: + :header-rows: 1 + :widths: 15, 30 + :class: center + + * - Class/Function + - Description + * - :obj:`settings ` + - Object for controlling SPy configuration options + * - :func:`algorithms.algorithms.iterator ` + - Function returning an iterator over image pixels diff --git a/_sources/class_func_ref.rst.txt b/_sources/class_func_ref.rst.txt new file mode 100644 index 0000000..f5cbf53 --- /dev/null +++ b/_sources/class_func_ref.rst.txt @@ -0,0 +1,376 @@ +.. _class-func-ref: + +.. toctree:: + :maxdepth: 2 + +============================ +Class/Function Documentation +============================ + +File I/O +======== + +open_image +---------- + +.. autofunction:: spectral.spectral.open_image + +ImageArray +---------- + +.. autoclass:: spectral.image.ImageArray + +SpyFile +------- + +.. autoclass:: spectral.SpyFile + :members: __str__, __getitem__, load + +SpyFile Subclasses +~~~~~~~~~~~~~~~~~~ + +SpyFile is an abstract base class. Subclasses of :class:`~spectral.io.spyfile.SpyFile` +(:class:`~spectral.io.bipfile.BipFile`, :class:`~spectral.io.bipfile.BilFile`, +:class:`~spectral.io.bsqfile.BsqFile`) all implement a common set of additional +methods. :class:`~spectral.io.bipfile.BipFile` is shown here but the other two are similar. + +.. autoclass:: spectral.io.bipfile.BipFile + :members: open_memmap, read_band, read_bands, read_pixel, read_subregion, read_subimage + + + +SubImage +-------- + +.. autoclass:: spectral.io.spyfile.SubImage + :members: read_band, read_bands, read_pixel, read_subregion, read_subimage + +File Formats +------------ + +AVIRIS +~~~~~~ + +.. automodule:: spectral.io.aviris + :members: open, read_aviris_bands + +ENVI +~~~~ + +.. automodule:: spectral.io.envi + :members: open, SpectralLibrary + +envi.create_image +----------------- + +.. autofunction:: spectral.io.envi.create_image + +envi.save_classification +------------------------ + +.. autofunction:: spectral.io.envi.save_classification + +envi.save_image +--------------- + +.. autofunction:: spectral.io.envi.save_image + +ERDAS/Lan +~~~~~~~~~ + +erdas.open +---------- + +.. automodule:: spectral.io.erdas + :members: open + +Graphics +======== + +ColorScale +---------- + +.. autoclass:: spectral.ColorScale + :members: __init__, __call__, set_background_color, set_range + +get_rgb +------- + +.. autofunction:: spectral.graphics.graphics.get_rgb + +ImageView +--------- + +.. autoclass:: spectral.graphics.spypylab.ImageView + :members: __init__, set_data, set_classes, set_source, show, label_region, refresh, set_display_mode, class_alpha, interpolation, open_zoom, pan_to, format_coord, set_rgb_options + +imshow +------ + +.. autofunction:: spectral.graphics.spypylab.imshow + +save_rgb +-------- + +.. autofunction:: spectral.save_rgb + +view +---- + +.. autofunction:: spectral.view + +view_cube +--------- + +.. autofunction:: spectral.view_cube + +view_indexed +------------ + +.. autofunction:: spectral.view_indexed + +view_nd +------- + +.. autofunction:: spectral.view_nd + + +Training Classes +================ + +create_training_classes +----------------------- + +.. autofunction:: spectral.create_training_classes + +TrainingClass +------------- + +.. autoclass:: spectral.algorithms.algorithms.TrainingClass + :members: __init__, __iter__, stats_valid, size, calc_stats, transform + :special-members: __init__, __iter__ + +TraningClassSet +--------------- + +.. autoclass:: spectral.algorithms.algorithms.TrainingClassSet + :members: add_class, transform, all_samples, __getitem__, __len__, __iter__ + +Spectral Classes/Functions +========================== + +Adaptive Coherence/Cosine Estimator (ACE) +----------------------------------------- + +.. autofunction:: spectral.algorithms.detectors.ace + +AsterDatabase +------------- + +.. autoclass:: spectral.database.AsterDatabase + :members: + +.. _spectral-band-info: + +BandInfo +-------- + +.. autoclass:: spectral.BandInfo + +BandResampler +------------- + +.. autoclass:: spectral.algorithms.resampling.BandResampler + :members: __init__, __call__ + +Bhattacharyya Distance +---------------------- + +.. autofunction:: spectral.algorithms.algorithms.bdist + +.. note:: + + Since it is unlikely anyone can actually remember how to spell + "Bhattacharyya", this function has been aliased to "bdist" + for convenience. + +calc_stats +---------- + +.. autofunction:: spectral.calc_stats + +covariance +---------- + +.. autofunction:: spectral.algorithms.algorithms.covariance + +cov_avg +------- + +.. autofunction:: spectral.algorithms.algorithms.cov_avg + +EcostressDatabase +----------------- + +.. autoclass:: spectral.database.EcostressDatabase + :members: + +FisherLinearDiscriminant +------------------------ + +.. autoclass:: spectral.algorithms.algorithms.FisherLinearDiscriminant + +GaussianClassifier +------------------ + +.. autoclass:: spectral.algorithms.classifiers.GaussianClassifier + :members: __init__, train, classify_spectrum + :special-members: __init__ + :inherited-members: classify_image + +GaussianStats +------------- + +.. autoclass:: spectral.algorithms.algorithms.GaussianStats + +kmeans +------ + +.. autofunction:: spectral.kmeans + +linear_discriminant +------------------- + +.. autofunction:: spectral.linear_discriminant + +LinearTransform +--------------- + +.. autoclass:: spectral.algorithms.transforms.LinearTransform + :members: __init__, __call__, chain + :special-members: __init__, __call__ + +MahalanobisDistanceClassifier +----------------------------- + +.. autoclass:: spectral.algorithms.classifiers.MahalanobisDistanceClassifier + :members: __init__, train, classify_spectrum + :special-members: __init__ + :inherited-members: classify_image + +map_class_ids +------------- + +.. autofunction:: spectral.algorithms.spatial.map_class_ids + +map_classes +----------- + +.. autofunction:: spectral.algorithms.spatial.map_classes + +matched_filter +-------------- + +.. autofunction:: spectral.algorithms.detectors.matched_filter + +MatchedFilter +------------- +.. autoclass:: spectral.algorithms.detectors.MatchedFilter + :members: __init__, whiten + :special-members: __init__, __call__ + +mean_cov +-------- + +.. autofunction:: spectral.algorithms.algorithms.mean_cov + +Minimum Noise Fraction (MNF) +---------------------------- + +.. autofunction:: spectral.algorithms.algorithms.mnf + +.. autoclass:: spectral.algorithms.algorithms.MNFResult + :members: denoise, get_denoising_transform, reduce, get_reduction_transform, num_with_snr + +msam +---- + +.. autofunction:: spectral.algorithms.algorithms.msam + +ndvi +---- + +.. autofunction:: spectral.algorithms.algorithms.ndvi + +noise_from_diffs +---------------- + +.. autofunction:: spectral.algorithms.algorithms.noise_from_diffs + +orthogonalize +------------- + +.. autofunction:: spectral.algorithms.algorithms.orthogonalize + +PerceptronClassifier +-------------------- + +.. autoclass:: spectral.algorithms.classifiers.PerceptronClassifier + :members: __init__, train, classify_spectrum + :inherited-members: classify_image + +Pixel Purity Index (PPI) +------------------------ + +.. autofunction:: spectral.algorithms.algorithms.ppi + +principal_components +-------------------- + +.. autofunction:: spectral.principal_components + +PrincipalComponents +------------------- + +.. autoclass:: spectral.algorithms.algorithms.PrincipalComponents + :members: reduce + :inherited-members: transform + +RX Anomaly Detector +------------------- + +.. autofunction:: spectral.algorithms.detectors.rx + +spectral_angles +--------------- + +.. autofunction:: spectral.algorithms.algorithms.spectral_angles + +SpectralLibrary +--------------- + +.. autoclass:: spectral.io.envi.SpectralLibrary + :members: + +transform_image +--------------- + +.. autofunction:: spectral.io.spyfile.transform_image + +Configuration +============= + +SpySettings +----------- + +.. autoclass:: spectral.config.SpySettings + +Utilities +========= + +iterator +-------- + +.. autofunction:: spectral.algorithms.algorithms.iterator + + diff --git a/_sources/fileio.rst.txt b/_sources/fileio.rst.txt new file mode 100644 index 0000000..5e95459 --- /dev/null +++ b/_sources/fileio.rst.txt @@ -0,0 +1,168 @@ +.. _fileio: + +.. toctree:: + :maxdepth: 2 + +.. |SpyFile| replace:: :class:`~spectral.SpyFile` + +.. _reading-data-files: + +======================== +Reading HSI Data Files +======================== + +The standard means of opening and accessing a hyperspectral image file with SPy +is via the :func:`~spectral.image` function, which returns an instance of a +|SpyFile| object. + +.. _spyfile-interface: + +The SpyFile Interface +===================== + +.. automodule:: spectral.io.spyfile + +.. _loading-images: + +Loading Entire Images +===================== + +It is important to note that image data are read by a |SpyFile| object on demand +and the data are not cached. Each time the |SpyFile| subscript operator or one of +the |SpyFile| read methods are called, data are read from the corresponding image +data file, regardless of whether the same data have been previously read. This +is done to avoid consuming too much memory when working with very large image files. +It also improves performance when performing operations that only require reading +a small portion of the data in a large image (e.g., reading RGB bands to display +the image). The downside of reading data on demand and not caching the data is that there can +be a significant run time penalty when running algorithms that require access to +all of the data. Performance will be even worse if the algorithm requires iterative +access to the data. + +To improve performance of spectral algorithms, it is preferable to load the entire +image into memory using the :meth:`~spectral.SpyFile.load` method, which returns +an :class:`~spectral.ImageArray` object. :class:`~spectral.ImageArray` provides +the full :class:`numpy.ndarray` interface, as well as the |SpyFile| interface. + +.. ipython:: + + In [10]: arr = img.load() + + In [11]: arr.__class__ + Out[11]: spectral.spectral.ImageArray + + In [12]: print(arr.info()) + # Rows: 145 + # Samples: 145 + # Bands: 220 + Data format: Float32 + + In [13]: arr.shape + Out[13]: (145, 145, 220) + +Because SPy is primarily designed for processing in the spectral domain, +:class:`spectral.ImageArray` objects in memory will always have data interleaved +by pixel, regardless of the interleave of the source image data file. In other +words, the :class:`numpy.ndarray` shape will be ``(numRows, numCols, numBands)``. +:class:`~spectral.ImageArray` objects always contain 32-bit floats. + +.. note:: + + Before calling the :meth:`load` method, it is important to consider the amount of memory + that will be consumed by the resulting ImageArray object. Since :class:`spectral.ImageArray` uses + 32-bit floating point values, the amount of memory consumed will be approximately + ``4 * numRows * numCols * numBands`` bytes. + +NumPy :class:`memmap` Interface +=============================== + +As an alternative to loading an entire image into memory, a somewhat slower +(but more memory efficient) way to access image data is to use a numpy memmap +object, as returned by the :meth:`~spectral.io.bipfile.BipFile.open_memmap` +method of SpyFile objects. memmap objects can also be used to write date to +an image file. + +.. _file-formats: + +File Formats Supported +====================== + +.. _envi-format: + +ENVI Headers +------------ + +ENVI [#envi-trademark]_ is a popular commercial software package for processing and analyzing +geospatial imagery. SPy can read images that have associated ENVI header files +and can read & write spectral libraries with ENVI headers. ENVI files are opened +automatically by the SPy :func:`~spectral.image` function but images can also be +opened explicitly as ENVI files. It may be necessary to open an ENVI file explicitly +if the data file is in a separate directory from the header or if the data file +has an unusual file extension that SPy can not identify. + +.. ipython:: + + In [14]: import spectral.io.envi as envi + + In [15]: img = envi.open('cup95eff.int.hdr', 'cup95eff.int') + + In [16]: import spectral.io.envi as envi + + In [17]: lib = envi.open('spectra.hdr') + + In [18]: lib.names[:5] + Out[18]: + ['construction asphalt', + 'construction concrete', + 'red smooth-faced brick', + 'weathered red brick', + 'bare red brick'] + +.. seealso:: Functions for writing image data to files: + + :func:`~spectral.io.envi.create_image`: + + Creates a new image file with allocated storage on disk. + + :func:`~spectral.io.envi.save_image`: + + Saves an existing image or ndarray to a file with an ENVI header. + +AVIRIS +------ + +SPy supports data files generated by the Airborne Visible/Infrared Imaging +Spectrometer (AVIRIS) [#aviris]_. AVIRIS files are automatically recognized by +the :func:`~spectral.open_image` function; however, spectral band calibration files +are not automatically recognized; therefore you may want to open the image as +an AVIRIS file explicitly and specify the cal file. + +.. ipython:: + + In [19]: img = aviris.open('f970619t01p02_r02_sc01.a.rfl', 'f970619t01p02_r02.a.spc') + +You can also load the band calibration file separately (this may be necessary if +the band calibration file is in AVIRIS format but the image is not). + +.. ipython:: + + In [20]: img = open_image('92AV3C.lan') + + In [21]: img.bands = aviris.read_aviris_bands('92AV3C.spc') + +ERDAS/Lan +--------- + +The ERDAS/Lan file format is automatically recognized by :func:`~spectral.image`. +It is unlikely that a file would need to be opened explicitly as a Lan file but it +can be done as follows. + +.. ipython:: + + In [22]: import spectral.io.erdas as erdas + + In [23]: img = erdas.open('92AV3C.lan') + + +.. [#envi-trademark] ENVI is a registered trademark of Exelis Visual Information Solutions. +.. [#aviris] `http://aviris.jpl.nasa.gov/ `_ diff --git a/_sources/graphics.rst.txt b/_sources/graphics.rst.txt new file mode 100644 index 0000000..2fed42d --- /dev/null +++ b/_sources/graphics.rst.txt @@ -0,0 +1,499 @@ +.. toctree:: + :maxdepth: 2 + +.. _graphics: + +=============== +Displaying Data +=============== + +.. ipython:: + :suppress: + + In [9]: import matplotlib + + In [9]: import matplotlib.pyplot as plt + + In [9]: matplotlib.use('Agg') + +.. _starting_ipython: + +Starting IPython +================ + +SPy uses IPython to provide GUI windows without blocking the interactive python +interpreter. To enable this, you must start IPython in "pylab" mode. If you +have already set your `matplotlib backend `_ +to either "WX" or "WXAgg" (see note below) in your matplotlibrc file, you +should be able to start IPython for SPy like this:: + + ipython --pylab + +You can also set the backend explicitly when starting IPython this way:: + + ipython --pylab=wx + +.. note:: + + If you are not calling GUI functions (calling :func:`~spectral.save_rgb` + doesn't count as a GUI function), then it is not necessary to run IPython - + you can run the standard python interpreter. + +.. note:: + + If you are not able to run the `WX` backend on your system, you can still use + a different backend (e.g., `Qt4Agg` or `TkAgg`); however you will be unable + to call the :func:`~spectral.view_cube` or + :func:`~spectral.view_nd` functions. + +Raster Displays +=============== + +The SPy :func:`~spectral.graphics.spypylab.imshow` function is a wrapper around +the matplotlib function of the same name. The main differences are that the SPy +version makes it easy to display bands from multispectral/hyperspectral images, +it renders classification images, and supports several additional types of +interactivity. + +Image Data Display +****************** + +The `imshow` function produces a raster display of data associated with an +np.ndarray or `SpyFile` object. + +.. ipython:: + + In [1]: from spectral import * + + In [1]: img = open_image('92AV3C.lan') + + @savefig imshow_92AV3C_rgb.png scale=75% align=center + In [1]: view = imshow(img, (29, 19, 9)) + +When displaying the image interactively, the matplotlib button controls can be +used to pan and zoom the displayed images. If you press the "z" keyboard key, +a zoom window will be opened, which displays a magnified view of the image. By +holding down the **CONTROL** key and left-clicking in the original window, the +zoom window will pan to the pixel clicked in the original window. + +.. versionchanged:: 0.16.0 + By default, `imshow` function applies a linear histogram stretch of the RGB + display data. The the color stretch can be controlled by the *stretch*, + *bounds*, and *stretch_all* keyword to the `imshow` function (see + :func:`~spectral.graphics.graphics.get_rgb` for the meaning of these + keywords). To adjust the color stretch of a displayed image, the + :meth:`~spectral.graphics.spypylab.ImageView.set_rgb_options` method of the + `ImageView` object can be called. + +RGB data limits for a displayed image can be printed from the `__str__` method +of the `ImageView` object: + +.. ipython:: + + In [1]: print(view) + +Class Map Display +***************** + +To display the ground truth image using :func:`imshow`, set the *classes* +argument in the `imshow` function: + +.. ipython:: + + In [1]: gt = open_image('92AV3GT.GIS').read_band(0) + + @savefig imshow_92AV3C_gt.png scale=75% align=center + In [9]: view = imshow(classes=gt) + +.. ipython:: + :suppress: + + In [9]: plt.close('all') + +It is also possible to switch between displaying image bands and class colors, +as well as displaying class color masks overlayed on the image data display. To +do this, specify both the data and class values when calling :func:`imshow`: + +.. ipython:: + + @verbatim + In [9]: view = imshow(img, (30, 20, 10), classes=gt) + +The default display mode is to show the image bands. Press "d", "c", +or "C" (while focus is on the image window) to switch the display to *data*, +*classes*, or *class overlay*, respectively. Setting the display parameters can also +be done programatically. For example, to display the image with overlayed class +masks, using an alpha transparency of 0.5, type the following commands after +calling :func:`imshow`: + +.. ipython:: + + In [9]: view = imshow(img, (30, 20, 10), classes=gt) + + In [9]: view.set_display_mode('overlay') + + In [9]: view.class_alpha = 0.5 + + @suppress + In [9]: plt.savefig('images/imshow_92AV3C_overlay.png') + +.. figure:: images/imshow_92AV3C_overlay.png + :align: center + :scale: 75 % + +Interactive Class Labeling +************************** + +The :class:`ImageView` window provides the ability to modify pixel class IDs interactively +by selecting rectangular regions in the image and assigning a new class ID. +Applying a class ID to a rectangular region is done using the following steps: + + #. Call :func:`imshow`, providing an initial array for the *classes* argument. + This must be a non-negative, integer-valued array. A class ID value of zero + represents an unlabeled pixel so to start with a completely unlabeled + image, pass an array of all zeros for the *classes* argument. + + #. While holding the **SHIFT** key, click with the left mouse button at the + upper left corner of the rectangle to be selected. Drag the mouse cursor + and release the mouse button at the lower right corner of the rectangular + region. Note that releasing the **SHIFT** key before the mouse button is + released will result in cancellation of the selection operation. + + #. With focus still on the :class:`ImageView` window, enter the numeric + class ID to apply to the region. The class ID can contain multiple digits. + The digits will not be echoed on the command line. Press **ENTER** to + apply the class ID. The command line will request confirmation of the + opertion. Press **ENTER** again to apply the class ID or press any other + key to cancel the operation. + +Classes can be assigned form either a main :class:`ImageView` window or from +an associated zoom window. While the selection tool only produces rectangular +regions, you can assign classes to non-rectangular regions by first assigning +the class ID to a super-rectangle covering all the pixels of interest, then +reassigning sub-rectangles back to class 0 (or whatever was the original class ID). + +Additional Capabilities +*********************** + +To get help on :class:`ImageView` keyboard & mouse functions, press "h" while focus is on +the image window to display all keybinds and mouse functions:: + + Mouse Functions: + ---------------- + ctrl+left-click -> pan zoom window to pixel + shift+left-click&drag -> select rectangular image region + left-dblclick -> plot pixel spectrum + + Keybinds: + --------- + 0-9 -> enter class ID for image pixel labeling + ENTER -> apply specified class ID to selected rectangular region + a/A -> decrease/increase class overlay alpha value + c -> set display mode to "classes" (if classes set) + C -> set display mode to "overlay" (if data and classes set) + d -> set display mode to "data" (if data set) + h -> print help message + i -> toggle pixel interpolation between "nearest" and SPy default. + z -> open zoom window + + See matplotlib imshow documentation for addition key binds. + +To customize behavior of the image display beyond what is provided by the +:func:`~spectral.graphics.spypylab.imshow` function, you can create an +:class:`~spectral.graphics.spypylab.ImageView` object directly and customize it +prior to calling its :meth:`show` method. You can also access the +matplotlib :attr:`figure` and :attr:`canvas` attributes from the :attr:`axes` +attribute of the :class:`ImageView` object returned by :func:`imshow`. Finally, +you can customize some of the default behaviours of the image display by +modifiying members of the module's :class:`~spectral.spectral.SpySettings` +object. + +Saving RGB Image Files +====================== + +To save an image display to a file, use the :func:`~spectral.save_rgb` function, +which uses the same arguments as :func:`~spectral.imshow` but with the saved image +file name as the first argument. + +.. ipython:: + + @verbatim + In [11]: save_rgb('rgb.jpg', img, [29, 19, 9]) + +Saving an indexed color image is similar to saving an RGB image; however, :func:`~spectral.save_rgb` +is unable to determine if the image being saved is a single-band (greyscale) image +or an indexed color image. Therefore, to save as an indexed color image, the color +palette must be explicitly passed as a keyword argument: + +.. ipython:: + + @verbatim + In [12]: save_rgb('gt.jpg', gt, colors=spy_colors) + + @suppress + In [13]: save_rgb('images/view_gt.jpg', gt, colors=spy_colors) + +Spectrum Plots +============== + +The image display windows provide a few interactive functions. If you create an +image display with :func:`~spectral.graphics.spypylab.imshow` (or :func:`~spectral.view`) +and then double-click on a particular location in the window, a new window will +be created with a 2D plot of the spectrum for the pixel that was clicked. It +should look something like this: + +.. figure:: images_static/rgb_double_click.png + :align: center + :scale: 50 % + +Note that the row/col of the double-clicked pixel is printed on the command prompt. +Since there is no spectral band metadata in our sample image file, the spectral +plot's axes are unlabeled and the pixel band values are plotted vs. band number, +rather than wavelength. To have the data plotted vs. wavelength, we must first +associated spectral band information with the image. + +.. ipython:: + + In [12]: import spectral.io.aviris as aviris + + In [13]: img.bands = aviris.read_aviris_bands('92AV3C.spc') + +Now, close the image and spectral plot windows, call :func:`~spectral.view` +again and click on a few locations in the image display. You will notice that +the x-axis now shows the wavelengths associated with each band. + +.. figure:: images_static/rgb_with_bands_double_click.png + :align: center + :scale: 50 % + +Notice that the spectra are now plotted against their associated wavelengths. + +The spectral plots are intended as a convenience to enable one to quickly view +spectra from an image. If you would like a prettier plot of the same data, you +can use SPy to read the spectra from the image and create a customized plot by +using `matplotlib `_ directly. + +Hypercube Display +================= + +In the context of hyperspectral imagery, a 3D hypercube is a 3-dimensional +representation of a hyperspectral image where the *x* and *y* dimensions are the spatial +dimensions of the image and the third dimension is the spectral dimension. The +analytical utility of hypercube displays is debatable but what is not debatable +is that they look extremely cool. + +After calling :func:`~spectral.view_cube`, +a new window will open with the 3D hypercube displayed. After shifting +command focus to the newly created window, you can use keyboard input to alter +the view of the hypercube. Using keyword arguments, you can change the image +displayed on the top of the cube, as well as the color scale used on the sides of +of the cube. + +.. ipython:: + + @verbatim + In [16]: view_cube(img, bands=[29, 19, 9]) + +.. code:: + + Mouse Functions: + ---------------- + left-click & drag -> Rotate cube + CTRL+left-click & drag -> Zoom in/out + SHIFT+left-click & drag -> Pan + + Keybinds: + --------- + l -> toggle light + t/g -> stretch/compress z-dimension + h -> print help message + q -> close window + +.. figure:: images_static/hypercube.jpg + :align: center + :scale: 50 % + +.. note:: + + If the window opened by :func:`~spectral.view_cube` produces a blank canvas + (no cube is displayed), it may be due to your display adapter not supporting + a 32-bit depth buffer. You can reduce the size of the depth buffer used by + :func:`~spectral.view_cube` and :func:`~spectral.view_nd` to a smaller value + (e.g., 16) by issuing the following commands: + +.. ipython:: + :verbatim: + + In [16]: import spectral + + In [16]: spectral.settings.WX_GL_DEPTH_SIZE = 16 + +.. _nd_displays: + +N-Dimensional Feature Display +============================= + +Since hyperspectral images contain hundreds of narrow, contiguous bands, there is often +strong correlation between bands (particularly adjacent bands). To increase the +amount of information displayed, it is common to reduce the dimensionality of +the image to a smaller set of features with higher information density (e.g., by +principal components transformation). Nevertheless, there are typically still +many more than three features remaining in the transformed image so the analyst +must decide which are the "best" three features to display to highlight some +aspect of the data set (e.g., separability of the spectral classes). It is +desirable to be able to quickly switch displayed features to examine many +combinations of features in a short time span. + +In most cases, the display will be more useful by first performing +dimensionality reduction prior to viewint the data (e.g., by selecting some +number of principal components). + +.. ipython:: + :verbatim: + + In [17]: data = open_image('92AV3C.lan').load() + + In [18]: gt = open_image('92AV3GT.GIS').read_band(0) + + In [19]: pc = principal_components(data) + Covariance.....done + + In [20]: xdata = pc.transform(data) + + In [21]: w = view_nd(xdata[:,:,:15], classes=gt) + +A new window should open and appear as follows: + +.. figure:: images_static/ndwindow_orig.png + :align: center + :scale: 80 % + + Initial view of the N-Dimensinal window + +The python prompt will display the set of keyboard and mouse commands accepted +by the ND window:: + + Mouse functions: + --------------- + Left-click & drag --> Rotate viewing geometry (or pan) + CTRL+Left-click & drag --> Zoom viewing geometry + CTRL+SHIFT+Left-click --> Print image row/col and class of selected pixel + SHIFT+Left-click & drag --> Define selection box in the window + Right-click --> Open GLUT menu for pixel reassignment + + Keyboard functions: + ------------------- + a --> Toggle axis display + c --> View dynamic raster image of class values + d --> Cycle display mode between single-quadrant, mirrored octants, + and independent octants (display will not change until features + are randomzed again) + f --> Randomize features displayed + h --> Print this help message + m --> Toggle mouse function between rotate/zoom and pan modes + p/P --> Increase/Decrease the size of displayed points + q --> Exit the application + r --> Reset viewing geometry + u --> Toggle display of unassigned points (points with class == 0) + +The following figure shows the same window after the display has been rotated, +unlabeled pixels (white) have been supressed, and the displayed pixel size has +been increased: + +.. figure:: images_static/ndwindow_mod.png + :align: center + :scale: 80 % + + Modified view of the N-Dimensinal window + + +Display Modes +************* + +To facilitate interactive visual analysis of high-dimensional images, the +:func:`~spectral.view_nd` function provides several modes for +displaying high-dimensional data in a 3-dimensional display window: + +#. Single-Octant Mode - In this mode, three features from the image are selected + and mapped to the x, y, and z axes in the + 3D window. The data are translated and scaled such that the displayed points + fill the positive x/y/z octant of the 3D space. The user can choose to + randomly select a new set of three features to display. With this randomization + capability, the user can rapidly cycle through various combinations of features + to identify desirable projections. + +#. Mirrored-Octant Mode - In this mode, three features are mapped to the + positive x, y, and z axes as in the single-octant mode. However, an + additional three features are selected and mapped the the negative portions + of the x, y, and z axes. The image pixels are then displayed in each of the eight + octants of the 3D display, using whichever features are associated with the + three semi-axes for the particular octant. In this mode, each partial plane + defined by two semi-axes can be considered a mirror, with the two + perpendicular semi-axes representing different features. For example, if the + x, y, z, -x, -y, and -z semi-axes are associated with features 1 through 6, + respectively, then the data points in the x/y/z and x/y/-z quadrants will + have a common projection onto the x/y plane. However, since z and -z are + mapped to different features, the data points will have different projections + along the z and -z axes. As with the single-octant mode, the user can + randomize the 6 features displayed. + +#. Independent-Octant Mode - In this mode, each octant of the 3D space represents + 3 independent features, enabling up to 24 features to be displayed. As with + the other two modes, the data points are translated and scaled such that the + entire set of image pixels completely fills each octant. It is possible that + a particular feature may be used in more than one octant. + +The user has the option of switching between display modes. Which display mode +is best may depend on a number of factors relating to the data set used or the +system on which the application is run. For images with sufficiently large +numbers of pixels, the system's display refresh rate may begin to suffer in +mirrored-octant or independent-octant mode because each image pixel must be +rendered 8 times. When performance is not an issue, the latter two modes provide +more simultaneous projections of the data that may enable a user to more quickly +explore the data set. + +User Interaction +**************** + +The initial 3D view of the rendered image data will almost certainly not be the +ideal 3D viewing geometry; therefore, the window allows the user to +manipulate the view by using the mouse input to zoom, pan, and rotate +the viewing geometry. The user can also increase/decrease the size of displayed +image pixels and toggle display of unlabeled (class 0) pixels. + +While pixel color allows the user to distinguish between classes of displayed +pixels, it may not be obvious to which class a given color is associated. To +identify the class of a specific pixel, the user can click on a pixel and the +python command line will print the pixel's location within the image +(row and column values) and the number (ID) of the class of the pixel. Since +clicking on a single screen pixel is difficult, the user can first increase the +size of the displayed image pixels such that the image pixel can easily be +selected with the mouse. + +Data Manipulation +***************** + +It is common for an analyst to want to review the results of unsupervised +classification and subsequently combine pixel classes or reassign pixels to new +or different classes. Similarly, an analyst may want to assess ground truth data and +possibly reassign pixels that were erroneously included in a class when the +ground truth image was developed. To support this capability, the N-D window +allows the user to click and drag with the mouse to select a rectangular region +and then reassign all pixels lying within the defined box. The application will +not only reassign the pixels visible within the selection box but also all pixels +within the projection of the box through the 3D space. + +When :func:`~spectral.view_nd` is called, it returns a proxy +object that has a `classes` member that represents the current array of class +values associated with the ND display. The proxy object can be used to access +the updated class array after image pixels have been reassigned through the ND +window. The proxy object also has a :meth:`set_features` method to specify the +set of features displayed in the 3D window. + +.. tip:: + + Before reassigning pixel class values, press the "c" key to open a second + window displaying a raster view of the current image pixel class values. Then, when + you reassign pixel classes in the ND window, the class raster view will + update automatically, providing and indication of which pixels in the source + image were modified. diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 0000000..933cd79 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,204 @@ +.. Spectral Python documentation master file, created by + sphinx-quickstart on Sun Jul 11 16:15:08 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. _home: + +Welcome to Spectral Python (SPy) +-------------------------------- + +.. figure:: images_static/hypercube_big.jpg + :scale: 70 % + :align: center + +Spectral Python (SPy) is a pure Python module for processing hyperspectral image +data. It has functions for reading, displaying, manipulating, and classifying +hyperspectral imagery. It can be used interactively from the Python command +prompt or via Python scripts. SPy is free, Open Source software distributed +under the `MIT License `_. +To see some examples of how SPy can be used, you may want to jump straight to +the documentation sections on :ref:`graphics` or :ref:`algorithms`. A +categorized listing of the main classes and functions are in the +:ref:`class-func-glossary`. You can download SPy from +`GitHub `_ +or the `Python Package Index (PyPI) `_. +See the :ref:`installation` section section of the documentation for details. + +Documentation +------------- + +.. toctree:: + :maxdepth: 2 + + Spectral Python User Guide + +News +---- + +2020-04-26 : As of version 0.21, Spectal Python (SPy) is released under the `MIT License `_. + +2019-10-06 : SPy 0.20 adds support for the ECOSTRESS spectral library. + +2017-06-04 : SPy 0.19 provides plotting support for bad band lists and adds a few utility methods. + +2016-06-18 : SPy 0.18 fixes several bugs and has improved ENVI header support. + +2015-11-11 : SPy 0.17 enables mapping class labels between images. + + Class labels can be mapped between images (e.g., from an unsupervised + classification result to a ground truth image) using :func:`~spectral.algorithms.spatial.map_class_ids` + and :func:`~spectral.algorithms.spatial.map_classes`. ENVI file handling + is improved, :func:`~spectral.view_nd` image windows support arbitrary + axis labels, and :class:`SpyFile` objects have improved numpy `ndarray` + interfaces. See the `SPy 0.17 release notes `_ + for details. And thanks to `Don March `_ for + many contributions to this release. + +2014-10-18 : SPy 0.16.0 provides initial support for Python 3. + + This release supports Python 3 for all functions except :func:`~spectral.view_cube` + and :func:`~spectral.view_nd`. Note that for Python 3, you should use the + Qt4Agg matplotlib backend. + + New features in this release include the Adaptive Coherence/Cosine Esimator (:func:`~spectral.algorithms.detectors.ace`) + target detector, Pixel Purity Index (:func:`~spectral.algorithms.algorithms.ppi`), + ability to save ENVI classification files (:func:`envi.save_classification `), + and linear contrast enhancement (by data limits or cumulative histogram percentiles). + The SPy :func:`~spectral.graphics.spypylab.imshow` function now applies + a 2% histogram color stretch by default (this can be overridden in the + :obj:`spectral.settings ` object). + + Additional info is in the `version 0.16.0 issues `_. + + +2014-06-04 : SPy 0.15.0 is released. + + This version adds the Minimum Noise Fraction algorithm + (:func:`~spectral.algorithms.algorithms.mnf`) + (a.k.a., Noise-Adjusted Principal Components). The related function + :func:`~spectral.algorithms.algorithms.noise_from_diffs` performs + estimation of image noise from a spectrally homogeneous region of the + image. + + :class:`SpyFile` read methods now accept an optional `use_memmap` argument that + provides finer control over when to use (or not use) the memmap interface + to read image file data. + + Many thanks to Don March (http://ohspite.net) for improving + ENVI header support (comment lines and blank parameters are now accepted) + and providing several performance improvements. + + Additional details are `here `_. + +2014-03-02 : SPy has moved! + + The Spectral Python web site is now located at `www.spectralpython.net `_. + All old URLs will automatically redirect to the new site. The primary source code + repository has also moved and is now hosted on GitHub at + `https://github.com/spectralpython/spectral `_. + For the indefinite future, source code and release builds will continue to + be mirrored on Sourceforge.net and as always, the current release can + always be installed from the `Python Package Index (PyPI) `_ + using `pip `_. + +2014-02-23 : SPy 0.14 is released. This is primarily a bug fix release. + + This release fixes a bug in :class:`~spectral.algorithms.classifiers.PerceptronClassifier` + and provides significant performance improvement. A bug is also fixed that + causes incorrect display of several faces in the :func:`~spectral.view_cube` + image cube display. See `VERSIONS.txt` file for full details. + +2014-01-06 : Numerous new user interface features and performance improvements in SPy 0.13. + + The SPy :func:`~spectral.graphics.spypylab.imshow` wrapper around matplotlib's + :func:`imshow` function provides numerous new features, including: + + * Interactive image class labeling using keyboard & mouse + + * Zoom windows + + * Class overlays with adjustable transparency + + * Dynamic view of changing pixel classes when modified in an ND Window. + + Data/Statistic cacheing and more efficient use of numpy provides significant + performance improvement in mutiple algorithms (GMLC 14x, Mahalanobis + classifier 8x, kmeans 3x). Functions :func:`~spectral.algorithms.detectors.rx` + and :func:`~spectral.algorithms.detectors.matched_filter` are significantly + faster, particularly when using common global covariance. + + The :func:`~spectral.algorithms.algorithms.cov_avg` function computes + covariance averaged over a set of classes (useful when samples are limited + or global covariance is desired). Christian Mielke provided code for the + :func:`~spectral.algorithms.algorithms.msam` function, which computes the + Modified SAM score (by Oshigami et al). + + +2013-09-06 : SPy 0.12 is released. + + SPy 0.12 provides an improved memmap interface that enables accessing image + data using arbitrary interleaves and supports editable images (see + :func:`~spectral.io.bipfile.BipFile.open_memmap` for details). The RX + anomaly detector (:func:`~spectral.algorithms.detectors.rx`) now allows + anomaly detection using local (sub-image) statistics by specifying an inner/outer + window around each pixel. The ability to disable algorithm progress messages + and addition of a wrapper around matplotlib's `imshow` function are provided to + simplify integration of SPy code with `IPython Notebooks `_. + +2013-04-03 : SPy 0.11 is released. + + This release adds an :class:`~spectral.algorithms.detectors.RX` anomaly detector, + ability to save and create images in ENVI format (see :func:`~spectral.io.envi.save_image` + and :func:`~spectral.io.envi.create_image`), and a unit-testing sub-package. + The top-level namespace has been simplified and several functions have been + renamed for consistency (:func:`image` is now :func:`~spectral.open_image` + and :func:`save_image` is now :func:`~spectral.save_rgb`). + +2013-02-23 : SPy 0.10.1 bug-fix release is now available. + + This is a bug-fix release that corrects the spectrum displayed when double- + clicking on a raster display. Version 0.10 introduced a bug that had the + row/column swapped, resulting in either the wrong pixel being plotted or an + exception raised. + + If you have installed SPy 0.10, you should install this update as soon as + possible. + +2013-02-17 : SPy 0.10 is released: SPy now uses IPython for GUI display. + + As of this release, SPy now uses IPython for non-blocking GUI windows. IPython + should be started in **pylab** mode with the appropriate backend set (see + :ref:`starting_ipython`). The standard python interpreter can still be used if + GUI functions are not being called. This release also resolves a number of + issues associated with different versions of wxWidgets (2.8.x vs. 2.9.x) on + various operating systems. + +2013-01-23 : SPy 0.9 adds a linear matched filter target detector. + + :class:`~spectral.algorithms.detectors.MatchedFilter` uses background and target means, along + with background covariance to provide a linear target detector. + + A generic :class:`~spectral.algorithms.transforms.LinearTransform` class allows simple application + of linear transforms to numpy ndarrays or :class:`~spectral.SpyFile` objects. + + +2012-07-10 : SPy 0.8 adds N-Dimensional visualization of hyperspectral image data. + + The :func:`~spectral.graphics.ndwindow.ndwindow` function enables viewing of + high-dimensional images in a 3D display. See :ref:`nd_displays` for details. + + Hypercube display now uses mouse control for pan/zoom/rotate. + + Fixed a bug in several deprecation warnings that caused infinte recursion. + +2012-02-19 : SPy 0.7 Released. + + The :func:`~spectral.kmeans` algorithm is about 10 times faster than version 0.6. Many + method/function names have been renamed for consistency with external packages. + A few bugs potentially affecting BIP and BSQ input have been fixed. + +2011-01-17 : SPy 0.6 Released. + + This release adds ASTER Spectral Library support, ability to save spectral + libraries, and installation via :mod:`distutils`. diff --git a/_sources/installation.rst.txt b/_sources/installation.rst.txt new file mode 100644 index 0000000..466f266 --- /dev/null +++ b/_sources/installation.rst.txt @@ -0,0 +1,114 @@ +.. _installation: + +******************************** +Installing SPy +******************************** + +.. toctree:: + :maxdepth: 2 + +SPy Dependencies +================ + +SPy requires Python and depends on several other freely available Python +modules. Prior to installing SPy, you should make sure its dependencies are met. +While you can use SPy to process hyperspectral data with just Python and NumPy, +there are several other modules you will need if you want to use any of SPy's +graphical capabilities. + +.. list-table:: SPy Dependencies + :header-rows: 1 + :widths: 20, 25 + :class: center + + * - Dependency + - Notes + * - `Python `_ 2.6+ or 3.3+ + - `(1)` + * - `NumPy `_ + - Required + * - `Pillow `_ or `Python Imaging Library (PIL) `_ + - Required if displaying or saving images + * - `wxPython `_ + - `(2)` + * - `matplotlib `_ + - Required if rendering raster displays or spectral plots + * - `IPython `_ + - Required for interactive, non-blocking GUI windows + * - `PyOpenGL `_ + - `(2)` + +Notes: + +(1): All SPy functions work with Python 3.3+ except :func:`~spectral.view_cube` and :func:`~spectral.view_nd`. + +(2): Required if calling :func:`~spectral.view_cube` or :func:`~spectral.view_nd`. + +As of SPy version 0.10, IPython is used to provide interactive GUI windows. +To use SPy with IPython, you will need to start IPython in "pylab" mode +(see :ref:`starting_ipython`). + +Installing from a distribution package +====================================== + +SPy is distributed as a Python source distribution, which can be downloaded from +the `Python Package Index (PyPI) `_ or +from the `SPy Project Page `_ on +GitHub. The source distribution will unpack to a directory with +a name like ``spectral-x.y``, where ``x.y`` is the SPy version number. To +install SPy, open a console in the unpacked directory and type the following: + +.. code:: + + python setup.py install + +Note that if you are on a unix-based system, you will either need to be logged +in as root or preface the above command with "sudo" (unless you use the -d +option to install it to a local directory). + +Installing with pip or Distribute +================================= + +If you have `Distribute `_ (or the +deprecated Setuptools) or `pip `_ installed on +your system, there is no need to download the latest SPy version explicitly. +If you have Distribute installed, simply type + +.. code:: + + easy_install spectral + +or using pip, type + +.. code:: + + pip install spectral + +Note that your pip binary may be named differently (e.g., "pip-python"). And +again, you may need to be logged in as root or use "sudo" if you are on a +unix-based system. + +Installing from the Git source code repository +============================================== + +The latest version of the SPy source code resides in the GitHub source code +repository. While the latest source code may be less stable than the most +recent release, it often has newer features and bug fixes. To download the +latest version of SPy from the Git repository, ``cd`` to the directory +where you would like to check out the source code and type the following:: + + git clone https://github.com/spectralpython/spectral.git + +Configuring SPy +=============== + +Because hyperspectral data files can be quite large, you might store all your +HSI data in one or several specific directories. To avoid having to type +absolute path names whenever you attempt to open an HSI file in SPy, you can +define a ``SPECTRAL_DATA`` environment variable, which SPy will use to find +image files (if they are not found in the current directory). ``SPECTRAL_DATA`` +should be a colon-delimited list of directory paths. + +.. seealso:: + + :class:`~spectral.spectral.SpySettings` diff --git a/_sources/libraries.rst.txt b/_sources/libraries.rst.txt new file mode 100644 index 0000000..4dfb6a4 --- /dev/null +++ b/_sources/libraries.rst.txt @@ -0,0 +1,259 @@ +.. _libraries: + +******************************** +Spectral Libraries +******************************** + +.. toctree:: + :maxdepth: 2 + +.. |EcostressDatabase| replace:: :class:`~spectral.database.EcostressDatabase` +.. |AsterDatabase| replace:: :class:`~spectral.database.AsterDatabase` + +ECOSTRESS Spectral Library +========================== + +The ECOSTRESS spectral library provides over 3,000 high-resolution spectra for +numerous classes of materials [Meerdink2019]_. The spectra and associated metadata +are provided as a large set of ASCII text files. SPy provides the ability to +import the ECOSTRESS library spectra and a subset of the associated metadata into +a relational databased that can be easily accessed from Python. + +You will first need to get a copy of the ECOSTRESS spectral library data files, +which can be requested `here `_. Once you have +acquired the library data, you can import the data in to an :py:mod:`sqlite` +database as follows: + +.. ipython:: + :verbatim: + + In [2]: db = spy.EcostressDatabase.create('ecostress.db', './eco_data_ver1') + Importing ./eco_data_ver1/vegetation.tree.betula.lenta.tir.bele-1-55.ucsb.nicolet.spectrum.txt. + Importing ./eco_data_ver1/vegetation.tree.quercus.lobata.tir.vh282.ucsb.nicolet.spectrum.txt. + Importing ./eco_data_ver1/nonphotosyntheticvegetation.branches.adenostoma.fasciculatum.vswir.vh334.ucsb.asd.spectrum.txt. + Importing ./eco_data_ver1/mineral.silicate.cyclosilicate.fine.tir.cs-2a.jpl.nicolet.spectrum.txt. + Importing ./eco_data_ver1/mineral.hydroxide.none.fine.tir.goethite_1.jhu.nicolet.spectrum.txt. + ---// snip //--- + Importing ./eco_data_ver1/mineral.silicate.inosilicate.coarse.vswir.in-8a.jpl.perkin.spectrum.txt. + Importing ./eco_data_ver1/vegetation.shrub.aloe.arborescens.tir.jpl077.jpl.nicolet.spectrum.txt. + Importing ./eco_data_ver1/mineral.silicate.phyllosilicate.coarse.tir.ps-12f.jpl.nicolet.spectrum.txt. + Processed 3403 files. + +.. note:: + + The ECOSTRESS library supercedes the older ASTER spectral library + [Baldridge2009]_. To import data from version 2.0 of the ASTER library, + follow the precedure above but with |AsterDatabase| instead of + |EcostressDatabase|. + +Once the database has been created, it can be accessed by instantiating an +|EcostressDatabase| object for the database file (it can also be +accessed by using sqlite external to Python). The current implementation of the +SPy database contains two tables: `Samples` and `Spectra`. There is a one-to-one +relationship between rows in the two tables but they have been separate to support +potential future changes to the database. The schemas for the tables are in the +:attr:`schemas` attribute of the database object: + +.. ipython:: + + @suppress + In [104]: from spectral import * + + In [105]: db = EcostressDatabase('ecostress.db') + + In [106]: for s in db.schemas: + .....: print(s) + .....: + CREATE TABLE Samples (SampleID INTEGER PRIMARY KEY, Name TEXT, Type TEXT, Class TEXT, SubClass TEXT, ParticleSize TEXT, SampleNum TEXT, Owner TEXT, Origin TEXT, Phase TEXT, Description TEXT) + CREATE TABLE Spectra (SpectrumID INTEGER PRIMARY KEY, SampleID INTEGER, SensorCalibrationID INTEGER, Instrument TEXT, Environment TEXT, Measurement TEXT, XUnit TEXT, YUnit TEXT, MinWavelength FLOAT, MaxWavelength FLOAT, NumValues INTEGER, XData BLOB, YData BLOB) + +Descriptions of most of the `Sample` table columns can be found in the ECOSTRESS +Spectral Library documentation. The sample spectra and bands are stored in +`BLOB` objects in the database, so you probably don't want to access them directly. +The recommended method is to use either the :meth:`~spectral.database.EcostressDatabase.get_spectrum` +or :meth:`~spectral.database.EcostressDatabase.get_signature` method of |EcostressDatabase|, +both of which take a `SpectrumID` as their argument. + +As a simple example, suppose you want to find all samples that have "stone" in +their name (this isn't the best way to find stones in the library but it makes +for an easy example). There are three ways you can issue queries through the +|EcostressDatabase| object. You can call its :meth:`~spectral.database.EcostressDatabase.print_query` +method to print query results to the command line. You can call its +:meth:`~spectral.database.EcostressDatabase.query` method to return query results in +tuples. Lastly, you can use the :attr:`cursor` attribute of the |EcostressDatabase| +object to issue the query. + +.. ipython:: + + In [107]: db.print_query('SELECT COUNT() FROM Samples WHERE Name LIKE "%stone%"') + 78 + + In [108]: db.print_query('SELECT SampleID, Name FROM Samples WHERE Name LIKE "%stone%" limit 10') + 43|slate stone shingle + 250|fossiliferous limestone + 251|dolomitic limestone + ---// snip //--- + 2435|limestone caco3 + 2436|limestone caco3 + +Next, let's retrieve and plot one of the results (we will take the last one). + +.. ipython:: + + @supress + In [117]: f = plt.figure() + + In [117]: s = db.get_signature(384) + + In [118]: import pylab as plt + + In [119]: plt.plot(s.x, s.y) + Out[119]: [] + + In [120]: plt.title(s.sample_name) + Out[120]: + + @savefig limestone.png scale=80% align=center + In [121]: plt.grid(1) + +.. seealso:: + + Module :py:mod:`sqlite3` + + The sqlite3 module is included with Python 2.5+. Details on sqlite3 + :obj:`connection` and :obj:`cursor` objects can be found in the + `Python sqlite3 documentation `_. + +ENVI Spectral Libraries +======================== + +While the |EcostressDatabase| provides a Python interface to the +ECOSTRESS Spectral Library, there may be times where you want to repeatedly access +a small, fixed subset of the spectra in the library and do not want to repeatedly +query the database. The ENVI file format enables storage of spectral libraries +(see :ref:`envi-format`). SPy can read these files into a SPy +:class:`~spectral.io.envi.SpectralLibrary` object. + +To enable easy creation of custom spectral libraries, the |EcostressDatabase| +has a :meth:`~spectral.database.EcostressDatabase.create_envi_spectral_library` method that +generates a spectral library that can easily be saved to ENVI format. Spectra +in the ECOSTRESS library have varying numbers of samples over varying spectral +ranges. To generate the library we want to save to ENVI format, we need to specify +a band discretization to which we want all of the desired spectra resampled. +Let's pick the bands from our sample hyperspectral image. + +.. ipython:: + + In [123]: bands = aviris.read_aviris_bands('92AV3C.spc') + + In [124]: print(bands.centers[0], bands.centers[-1]) + 400.019989 2498.959961 + +We see from the output above that the bands range from about 400 - 2,500 `nm` +(we're ignoring the fact that the bands at both ends have a finite width). +We would like to find library spectra that cover the entire spectral range for +our image, so we'll check the band limits for the library spectra. But first, +let's check the units of the spectra in the library. + +.. ipython:: + + In [125]: db.print_query('SELECT DISTINCT Measurement, XUnit, YUnit FROM Samples, Spectra ' + + .....: 'WHERE Samples.SampleID = Spectra.SampleID AND ' + + .....: 'Name LIKE "%stone%"') + directional (10 degree) hemispherical reflectance|wavelength (micrometers)|reflectance (percent) + directional hemispherical reflectance|wavelength (micrometers)|reflectance (percent) + hemispherical reflectance|wavelength (micrometers)|reflectance (percent) + reflectance|wavelength (micrometers)|reflectance (percent) + +We see that all spectra are measures of reflectance but wavelengths are in units +of micrometers, whereas our sample image bands are in nanometers. To properly +query the spectra, we will need to specify the band limits in micrometers. + +.. ipython:: + + In [126]: db.print_query('SELECT COUNT() FROM Samples, Spectra ' + + .....: 'WHERE Samples.SampleID = Spectra.SampleID AND ' + + .....: 'Name LIKE "%stone%" AND ' + + .....: 'MinWavelength <= 0.4 AND MaxWavelength >= 2.5') + 59 + +So it appears that 59 of the 78 "stone" spectra cover the desired band limits. +To create a new, resampled spectral library for these spectra, we call the +|EcostressDatabase| :meth:`~spectral.database.EcostressDatabase.create_envi_spectral_library` +method, passing it the list of spectrum IDs and our output band schema (in micrometers). +Since our bands are defined in nanometers, we will convert them before calling +the method. + +.. ipython:: + + In [127]: rows = db.query('SELECT SpectrumID FROM Samples, Spectra ' + + .....: 'WHERE Samples.SampleID = Spectra.SampleID AND ' + + .....: 'Name LIKE "%stone%" AND ' + + .....: 'MinWavelength <= 0.4 AND MaxWavelength >= 2.5') + + In [128]: ids = [r[0] for r in rows] + + In [128]: print(ids) + + In [129]: bands.centers = [x / 1000. for x in bands.centers] + + In [130]: bands.bandwidths = [x / 1000. for x in bands.bandwidths] + + In [131]: lib = db.create_envi_spectral_library(ids, bands) + +Now that we've created a resampled library of spectra, let's plot the last +spectrum in the resampled library. + +.. ipython:: + + @supress + In [132]: f = plt.figure() + + In [133]: s = db.get_signature(ids[-1]) + + In [134]: plt.plot(s.x, s.y, 'k-', label='original'); + + In [136]: plt.plot(bands.centers, lib.spectra[-1], 'r-', label='resampled'); + + In [137]: plt.grid(1) + + In [138]: plt.gca().legend(loc='upper left'); + + In [138]: plt.xlim(0, 3); + + @savefig spectrum_resampled.png scale=80% align=center + In [139]: plt.title('Resampled %s spectrum' % lib.names[-1]); + +The resampled spectral library can be used with any image that uses the same +band calibration to which we resampled the spectra. We can also save the library +for future use. But before we save the library, we need to change the band units to the units +used in the band calibration (we need to convert from micrometers to nanometers). + +.. ipython:: + + In [141]: lib.bands.centers = [1000. * x for x in lib.bands.centers] + + In [142]: lib.bands.bandwidths = [1000. * x for x in lib.bands.bandwidths] + + In [143]: lib.bands.band_unit = 'nm' + + In [144]: lib.save('stones', 'Stone spectra from the ECOSTRESS library') + +Saving the library with the name "stones" creates two files: "stones.sli" and +"stones.hdr". The first file contains the resampled spectra and the second is the +header file that we use to open the library. + +.. ipython:: + + In [145]: mylib = envi.open('stones.hdr') + + In [146]: mylib + Out[146]: + +.. rubric:: References + +.. [Baldridge2009] Baldridge, A. M., Hook, S. J., Grove, C. I. and G. Rivera, 2008(9). + The ASTER Spectral Library Version 2.0. In press Remote Sensing of Environment + +.. [Meerdink2019] Meerdink, S. K., Hook, S. J., Roberts, D. A., & Abbott, E. A. (2019). + The ECOSTRESS spectral library version 1.0. Remote Sensing of Environment, 230(111196), 1–8. diff --git a/_sources/user_guide.rst.txt b/_sources/user_guide.rst.txt new file mode 100644 index 0000000..e5169ce --- /dev/null +++ b/_sources/user_guide.rst.txt @@ -0,0 +1,20 @@ +.. _user_guide: + +******************************** +Spectral Python (SPy) User Guide +******************************** + + :Release: |version| + :Date: |today| + +.. toctree:: + :maxdepth: 2 + + Introduction + Installation + Reading Hyperspectral Data + Displaying Data + Spectral Algorithms + Spectral Libraries + Classes/Function Glossary + Class/Function Documentation diff --git a/_sources/user_guide_intro.rst.txt b/_sources/user_guide_intro.rst.txt new file mode 100644 index 0000000..ceff04d --- /dev/null +++ b/_sources/user_guide_intro.rst.txt @@ -0,0 +1,39 @@ +.. _user-guide-intro: + +=================== +Introduction +=================== + +This user guide introduces various categories of SPy functions in a tutorial +style. If you would like to test the commands presented in the guide, you +should download the following sample data files, which are associated with a +well-studied AVIRIS hyperspectral image collected over Indiana in 1992. +[Landgrebe1998]_ + + +.. list-table:: Sample Data Files + :header-rows: 1 + :widths: 5, 30 + :class: center + + * - File Name + - Description + * - :download:`92AV3C.lan ` + - A small hyperspectral image chip (9.3 MB) in ERDAS/Lan format. The chip + is 145x145 pixels from an AVIRIS image and contains 220 spectral bands. + * - :download:`92AV3GT.GIS ` + - A land-use ground-truth map for the hyperspectral image chip in ERDAS/Lan + format. + * - :download:`92AV3C.spc ` + - An AVIRIS-formatted band calibration file for the image chip. + +Many of the examples presented in the guide are cumulative, with success of +commands issued depending on previous commands and module imports. While it is +generally not a good idea to import the contents of entire module namespaces, +for brevity, the examples in the user guide assume that ``from spectral import *`` +has been issued. + +.. rubric:: References + +.. [Landgrebe1998] Landgrebe, D. Multispectral data analysis: A signal theory + perspective. School of Electr. Comput. Eng., Purdue Univ., West Lafayette, IN (1998). diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..0119285 --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,768 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > p:first-child, +td > p:first-child { + margin-top: 0px; +} + +th > p:last-child, +td > p:last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +li > p:first-child { + margin-top: 0px; +} + +li > p:last-child { + margin-bottom: 0px; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > p:first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/classic.css b/_static/classic.css new file mode 100644 index 0000000..b4fd80f --- /dev/null +++ b/_static/classic.css @@ -0,0 +1,266 @@ +/* + * classic.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- classic theme. + * + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +html { + /* CSS hack for macOS's scrollbar (see #1125) */ + background-color: #FFFFFF; +} + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th, dl.field-list > dt { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/_static/covariance.png b/_static/covariance.png new file mode 100644 index 0000000..1844314 Binary files /dev/null and b/_static/covariance.png differ diff --git a/_static/default.css b/_static/default.css new file mode 100644 index 0000000..81b9363 --- /dev/null +++ b/_static/default.css @@ -0,0 +1 @@ +@import url("classic.css"); diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..daccd20 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,315 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..88f21cf --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,11 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '0.21', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/fld3.png b/_static/fld3.png new file mode 100644 index 0000000..cd08343 Binary files /dev/null and b/_static/fld3.png differ diff --git a/_static/fld_training.png b/_static/fld_training.png new file mode 100644 index 0000000..4e3cd95 Binary files /dev/null and b/_static/fld_training.png differ diff --git a/_static/fld_training_errors.png b/_static/fld_training_errors.png new file mode 100644 index 0000000..f3bc066 Binary files /dev/null and b/_static/fld_training_errors.png differ diff --git a/_static/gmlc2_errors.png b/_static/gmlc2_errors.png new file mode 100644 index 0000000..7002df0 Binary files /dev/null and b/_static/gmlc2_errors.png differ diff --git a/_static/gmlc2_training.png b/_static/gmlc2_training.png new file mode 100644 index 0000000..2874868 Binary files /dev/null and b/_static/gmlc2_training.png differ diff --git a/_static/gmlc_errors.png b/_static/gmlc_errors.png new file mode 100644 index 0000000..345af67 Binary files /dev/null and b/_static/gmlc_errors.png differ diff --git a/_static/gmlc_map.png b/_static/gmlc_map.png new file mode 100644 index 0000000..4b34597 Binary files /dev/null and b/_static/gmlc_map.png differ diff --git a/_static/gmlc_map_training.png b/_static/gmlc_map_training.png new file mode 100644 index 0000000..440812b Binary files /dev/null and b/_static/gmlc_map_training.png differ diff --git a/_static/imshow_92AV3C_gt.png b/_static/imshow_92AV3C_gt.png new file mode 100644 index 0000000..b74d46b Binary files /dev/null and b/_static/imshow_92AV3C_gt.png differ diff --git a/_static/imshow_92AV3C_rgb.png b/_static/imshow_92AV3C_rgb.png new file mode 100644 index 0000000..1095898 Binary files /dev/null and b/_static/imshow_92AV3C_rgb.png differ diff --git a/_static/jquery-3.4.1.js b/_static/jquery-3.4.1.js new file mode 100644 index 0000000..773ad95 --- /dev/null +++ b/_static/jquery-3.4.1.js @@ -0,0 +1,10598 @@ +/*! + * jQuery JavaScript Library v3.4.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2019-05-01T21:04Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.4.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a global context + globalEval: function( code, options ) { + DOMEval( code, { nonce: options && options.nonce } ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.4 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2019-04-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) && + + // Support: IE 8 only + // Exclude object elements + (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && rdescend.test( selector ) ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = (elem.ownerDocument || elem).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( typeof elem.contentDocument !== "undefined" ) { + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + } ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + // Support: IE 9-11 only + // Also use offsetWidth/offsetHeight for when box sizing is unreliable + // We use getClientRects() to check for hidden/disconnected. + // In those cases, the computed value can be trusted to be border-box + if ( ( !support.boxSizingReliable() && isBorderBox || + val === "auto" || + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = Date.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url, options ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + +
+
+
+
+ +
+
+
+

Spectral Algorithms

+

SPy implements various algorithms for dimensionality reduction and supervised & +unsupervised classification. Some of these algorithms are computationally +burdensome and require iterative access to image data. These algorithms will +almost always execute significantly faster if the image data is loaded into +memory. Therefore, if you use these algorithms and have sufficient computer +memory, you should load your image into memory.

+
In [1]: from spectral import *
+
+In [2]: img = open_image('92AV3C.lan').load()
+
+
+
+

Unsupervised Classification

+

Unsupervised classification algorithms divide image pixels into groups based on +spectral similarity of the pixels without using any prior knowledge of the +spectral classes.

+
+

k-means Clustering

+

The k-means algorithm takes an iterative approach to generating clusters. +The parameter k specifies the desired number of clusters to generate. The algorithm +begins with an initial set of cluster centers (e.g., results from cluster). +Each pixel in the image is then assigned to the nearest cluster center (using +distance in N-space as the distance metric) and each cluster center is then recomputed +as the centroid of all pixels assigned to the cluster. This process repeats until +a desired stopping criterion is reached (e.g., max number of iterations). To run +the k-means algorithm on the image and create 20 clusters, using a maximum of +50 iterations, call kmeans as follows.

+
In [3]: (m, c) = kmeans(img, 20, 30)
+Initializing clusters along diagonal of N-dimensional bounding box.
+Starting iterations.
+Iteration 1...done
+        21024 pixels reassigned.
+Iteration 2...done
+        11214 pixels reassigned.
+Iteration 3...done
+        4726 pixels reassigned.
+---// snip //---
+Iteration 28...done
+        248 pixels reassigned.
+Iteration 29...done
+        215 pixels reassigned.
+Iteration 30...done
+        241 pixels reassigned.
+^CIteration 31... 15.9%KeyboardInterrupt: Returning clusters from previous iteration
+
+
+

Note that I interrupted the algorithm with a keyboard interrupt (CTRL-C) after +the 30th iteration since there were only about a hundred pixels migrating between +clusters at that point. kmeans catches the KeyboardInterrupt +exception and returns the clusters generated at the end of the previous +iteration. If you are running the algorithm interactively, this feature allows +you to set the max number of iterations to an arbitrarily high number and then +stop the algorithm when the clusters have converged to an acceptable level. If you +happen to set the max number of iterations too small (many pixels are still migrating +at the end of the final iteration), you can simply call kmeans again +to resume processing by passing the cluster centers generated by the previous call +as the optional start_clusters argument to the function.

+
+_images/kmeans_20_30.jpg +

k-means clustering results

+
+

Notice that kmeans appears to have captured much more of the +spectral variation in the image than the single-pass cluster +function. Let’s also take a look at the the cluster centers produced by the +algorithm:

+
In [4]: import matplotlib.pyplot as plt
+
+In [5]: plt.figure()
+
+In [6]: for i in range(c.shape[0]):
+   ...:     plt.plot(c[i])
+   ...: 
+
+In [7]: plt.grid()
+
+
+
+_images/kmeans_20_30_centers.png +

k-means cluster centers

+
+
+
+
+

Supervised Classification

+
+

Training Data

+

Performing supervised classification requires training a classifier with training +data that associates samples with particular training classes. To assign class +labels to pixels in an image having M rows and N columns, you must provide an +MxN integer-valued ground truth array whose elements are indices for the corresponding +training classes. A value of 0 in the ground truth array indicate an unlabeled pixel +(the pixel is not associated with a training class).

+

The following commands will load and display the ground truth map for our sample +image:

+
In [8]: gt = open_image('92AV3GT.GIS').read_band(0)
+
+In [9]: v = imshow(classes=gt)
+
+
+_images/view_gt.png +

We can now create a TrainingClassSet object by calling +create_training_classes:

+
In [10]: classes = create_training_classes(img, gt)
+
+
+

With the training classes defined, we can then create a supervised classifier, +train it with the training classes, and then classify an image.

+
+
+

Gaussian Maximum Likelihood Classification

+

In this case, we’ll perform Gaussian Maximum Likelihood Classification (GMLC), +so let’s create the appropriate classifier.

+
In [11]: gmlc = GaussianClassifier(classes)
+
+
+

When we created the classifier, it was automatically trained on the training sets +we provided. Notice that the classifier ignored five of the training classes. +GMLC requires computing the inverse of the covariance matrix for each training +class. Since our sample image contains 220 spectral bands, classes with fewer +than 220 samples will have singular covariance matrices, for which we can’t compute +the inverse.

+

Once the classifier is trained, we can use it to classify an image having +the same spectral bands as the training set. Let’s classify our training image +and display the resulting classification map.

+
In [12]: clmap = gmlc.classify_image(img)
+
+In [13]: v = imshow(classes=clmap)
+
+
+_images/gmlc_map.png +

The classification map above shows classification results for the entire image. To view +results for only the ground truth pixels we must mask out all the pixels not +associated with a training class.

+
In [14]: gtresults = clmap * (gt != 0)
+
+In [15]: v = imshow(classes=gtresults)
+
+
+_images/gmlc_map_training.png +

If the classification results are good, we expect the classification map +above to look very similar to the original ground truth map. To view only the +errors, we must mask out all elements in gtResults that do not match the +ground truth image.

+
In [16]: gterrors = gtresults * (gtresults != gt)
+
+In [17]: v = imshow(classes=gterrors)
+
+
+_images/gmlc_errors.png +

The five contiguous regions in the error image above correspond to the ground +truth classes that the GaussianClassifier ignored because they +had too few samples.

+

The following table lists the supervised classifiers SPy currently provides.

+ ++++ + + + + + + + + + + + + + + + + +

Classifier

Description

GaussianClassifier

Gaussian Maximum Likelihood

MahalanobisDistanceClassifier

Mahalanobis Distance

PerceptronClassifier

Multi-Layer Perceptron

+
+
+
+

Dimensionality Reduction

+

Processing hyperspectral images with hundreds of bands can be computationally +burdensome and classification accuracy may suffer due to the so-called “curse +of dimensionality”. To mitigate these problems, it is often desirable to reduce +the dimensionality of the data.

+
+

Principal Components

+

Many of the bands within hyperspectral images are often strongly correlated. +The principal components transformation represents a linear transformation of the +original image bands to a set of new, uncorrelated features. These new features +correspond to the eigenvectors of the image covariance matrix, where the associated +eigenvalue represents the variance in the direction of the eigenvector. A very +large percentage of the image variance can be captured in a relatively small +number of principal components (compared to the original number of bands) .

+

The SPy function principal_components computes the principal +components of the image data and returns the mean, covariance, eigenvalues, and +eigenvectors in a PrincipalComponents. +This object also contains a transform to rotate data in to the space of the +principal compenents, as well as a method to reduce the number of eigenvectors.

+
In [18]: pc = principal_components(img)
+
+In [19]: v = imshow(pc.cov)
+
+
+_images/covariance.png +

In the covariance matrix display, whiter values indicate strong positive covariance, +darker values indicate strong negative covariance, and grey values indicate +covariance near zero.

+

To reduce dimensionality using principal components, we can sort the eigenvalues +in descending order and then retain enough eigenvalues (an corresponding eigenvectors) +to capture a desired fraction of the total image variance. We then reduce the +dimensionality of the image pixels by projecting them onto the remaining eigenvectors. +We will choose to retain a minimum of 99.9% of the total image variance.

+
In [20]: pc_0999 = pc.reduce(fraction=0.999)
+
+In [21]: # How many eigenvalues are left?
+
+In [22]: len(pc_0999.eigenvalues)
+Out[22]: 32
+
+In [23]: img_pc = pc_0999.transform(img)
+
+In [24]: v = imshow(img_pc[:,:,:3], stretch_all=True)
+
+
+_images/pc3.png +

Now we’ll use a Gaussian maximum likelihood classifier (GMLC) for the reduced +principal components to train and classify against the training data.

+
In [25]: classes = create_training_classes(img_pc, gt)
+
+In [26]: gmlc = GaussianClassifier(classes)
+
+In [27]: clmap = gmlc.classify_image(img_pc)
+
+In [28]: clmap_training = clmap * (gt != 0)
+
+In [29]: v = imshow(classes=clmap_training)
+
+
+_images/gmlc2_training.png +

And the associated errors:

+
In [30]: training_errors = clmap_training * (clmap_training != gt)
+
+In [31]: v = imshow(classes=training_errors)
+
+
+_images/gmlc2_errors.png +
+
+

Fisher Linear Discriminant

+

The Fisher Linear Discriminant (a.k.a., canonical discriminant) attempts to +find a set of transformed axes that maximize the ratio of the average distance +between classes to the average distance between samples within each class. [Richards1999] +This is written as

+
+

C_b x = \lambda C_w x

+

where C_b is the covariance of the class centers and C_w is the +weighted average of the covariances of each class. If C_w is invertible, +this becomes

+
+

\left(C_{w}^{-1} C_b\right)x=\lambda x

+

This eigenvalue problem is solved by the linear_discriminant +function, yielding C-1 eigenvalues, where C is the number of classes.

+
In [32]: classes = create_training_classes(img, gt)
+
+In [33]: fld = linear_discriminant(classes)
+
+In [34]: len(fld.eigenvectors)
+Out[34]: 220
+
+
+

Let’s view the image projected onto the top 3 components of the transform:

+
In [35]: img_fld = fld.transform(img)
+
+In [36]: v = imshow(img_fld[:, :, :3])
+
+
+_images/fld3.png +

Next, we’ll classify the data using this discriminant.

+
In [37]: classes.transform(fld.transform)
+
+In [38]: gmlc = GaussianClassifier(classes)
+
+In [39]: clmap = gmlc.classify_image(img_fld)
+
+In [40]: clmap_training = clmap * (gt != 0)
+
+In [41]: v = imshow(classes=clmap_training)
+
+
+_images/fld_training.png +
In [42]: fld_errors = clmap_training * (clmap_training != gt)
+
+In [43]: v = imshow(classes=fld_errors)
+
+
+_images/fld_training_errors.png +
+

See also

+

orthogonalize:

+
+

Gram-Schmidt Orthogonalization

+
+
+
+
+
+

Target Detectors

+
+

RX Anomaly Detector

+

The RX anomaly detector uses the squared Mahalanobis distance as a measure of +how anomalous a pixel is with respect to an assumed background. The SPy +rx function computes RX scores for an +array of image pixels. The squared Mahalanobis distance is given by

+
+

y=(x-\mu_b)^T\Sigma_b^{-1}(x-\mu_b)

+

where x is the pixel spectrum, \mu_b is the background +mean, and \Sigma_b is the background covariance [Reed_Yu_1990].

+

If no background statistics are passed to the rx function, +background statistics will be estimated from the array of pixels for which the +RX scores are to be calculated.

+
In [44]: rxvals = rx(img)
+
+
+

To declare pixels as anomalous, we need to specify a threshold RX score. For +example, we could choose all image pixels whose RX score has a probability of +less than 0.001 with respect to the background:

+
In [45]: from scipy.stats import chi2
+
+In [46]: nbands = img.shape[-1]
+
+In [47]: P = chi2.ppf(0.999, nbands)
+
+In [48]: v = imshow(1 * (rxvals > P))
+
+
+_images/rxvals_threshold.png +

Rather than specifying a threshold for anomalous pixels, one can also simply +view an image of raw RX scores, where brighter pixels are considered “more anomalous”:

+
In [49]: v = imshow(rxvals)
+
+
+_images/rxvals.png +

For the sample image, only a few pixels are visible in the image of RX scores +because a linear color scale is used and there is a very small number of pixels with RX +scores much higher than other pixels. This is apparent from viewing the histogram +of the RX scores.

+
In [50]: import matplotlib.pyplot as plt
+
+In [51]: f = plt.figure()
+
+In [52]: h = plt.hist(rxvals.ravel(), 200, log=True)
+
+In [53]: h = plt.grid()
+
+
+_images/rxhistogram.png +

The outliers are not obvious in the histogram, so let’s print their values:

+
In [54]: print(np.sort(rxvals.ravel())[-10:])
+[ 665.17211462  675.85801536  683.58190673  731.4872873   739.95211335
+  906.98669373  956.49972325 1703.20957949 2336.11246149 9018.65517253]
+
+
+

To view greater detail in the RX image, we can adjust the lower and upper limits +of the image display. Since we are primarily interested in the most anomalous +pixels, we will set the black level to the 99th percentile of the RX values’ +cumulative histogram and set the white point to the 99.99th percentile:

+
In [55]: v = imshow(rxvals, stretch=(0.99, 0.9999))
+
+
+_images/rxvals_stretched.png +

We can see the new RGB data limits by inspecting the returned +ImageView object:

+
In [56]: print(v)
+ImageView object:
+  Display bands       :  [0]
+  Interpolation       :  <default>
+  RGB data limits     :
+    R: [291.6938067178437, 956.4997232540622]
+    G: [291.6938067178437, 956.4997232540622]
+    B: [291.6938067178437, 956.4997232540622]
+
+
+

Note that we could also have set the contrast stretch to explicit RX values +(vice percentile values) by specifying the bounds keyword instead of stretch.

+

If an image contains regions with different background materials, then the +assumption of a single mean/covariance for background pixels can reduce +performance of the RX anomaly detector. In such situations, better results +can be obtained by dynamically computing background statistics in a neighborhood +around each pixel being evaluated.

+

To compute local background statistics for +each pixel, the rx function accepts an optional window argument, which +specifies an inner/outer window within which to calculate background statistics +for each pixel being evaluated. The outer window is the window within which +background statistics will be calculated. The inner window is a smaller window +(within the outer window) indicating an exclusion zone within which pixels are +to be ignored. The purpose of the inner window is to prevent potential anomaly/target +pixels from “polluting” background statistics.

+

For example, to compute RX scores with background statistics computed from a +21 \times 21 pixel window about the pixel being evaluated, with an exclusion window of +5 \times 5 pixels, the function would be called as follows:

+
In [57]: rxvals = rx(img, window=(5,21))
+
+
+

While the use of a windowed background will often improve results for images containing +multiple background materials, it does so at the expense of introducing two +issues. First, the sizes of the inner and outer windows must be specified such +that the resulting covariance has full rank. That is, if w_{in} and +w_{out} represent the pixel widths of the inner and outer windows, respectively, +then w_{out}^2 - w_{in}^2 must be at least as large as the number of +image bands. Second, recomputing the estimated background covariance for each +pixel in the image makes the computational complexity of of the RX score +computation orders of magnitue greater.

+

As a compromise between a fixed background and recomputation of mean & covariance +for each pixel, rx can be passed a global covariance estimate in addition +to the window argument. In this case, only the background mean within the window will +be recomputed for each pixel. This significanly reduces computation time +for the windowed algorithm and removes the size limitation on the window (except +that the outer window must be larger than the inner). For example, since our sample image has ground +cover classes labeled, we can compute the average covariance over those ground +cover classes and use the result as an estimate of the global covariance.

+
In [58]: C = cov_avg(img, gt)
+
+In [59]: rxvals = rx(img, window=(5,21), cov=C)
+
+
+
+
+

Matched Filter

+

The matched filter is a linear detector given by the formula

+
+

y=\frac{(\mu_t-\mu_b)^T\Sigma_b^{-1}(x-\mu_b)}{(\mu_t-\mu_b)^T\Sigma^{-1}(\mu_t-\mu_b)}

+

where \mu_t is the target mean, \mu_b is the background +mean, and \Sigma_b is the covariance. The matched filter response is +scaled such that the response is zero when the input is equal to the background +mean and equal to one when the pixel is equal to the target mean. Like the +rx function the SPy +matched_filter function will estimate +background statistics from the input image if no background statistics are +specified.

+

Let’s select the image pixel at (row, col) = (8, 88) as our target, use a +global background statistics estimate, and plot all pixels whose matched filter +scores are greater than 0.2.

+
In [60]: t = img[8, 88]
+
+In [61]: mfscores = matched_filter(img, t)
+
+In [62]: v = imshow(1 * (mfscores > 0.2))
+
+
+_images/mf_gt_02.png +

As with the rx function, matched_filter can be applied using +windowed background statistics (optionally with a global covariance estimate).

+
+

See also

+

ace:

+
+

Adaptive Coherence/Cosine Estimator (ACE)

+
+
+
+
+
+

Miscellaneous Functions

+
+

Band Resampling

+

Comparing spectra measured with a particular sensor to spectra collected by a +different sensor often requires resampling spectra to a common band discretization. +Spectral bands of a single sensor may drift enough over time such that spectra +collected by the same sensor at different dates requires resampling.

+

For resampling purposes, SPy treats a sensor as having Gaussian spectral response +functions for each of its spectral bands. A source sensor band will contribute +to any destination band where there is overlap between the FWHM of the response +functions of the two bands. If there is an overlap, an integral is +performed over the region of overlap assuming the source band data value is +constant over its FWHM (since we do not know the true spectral load over the +source band) and the destination band has a Gaussian response function. Any +target bands that do not have an overlapping source band will produce NaN +as the resampled band value. If FWHM information is not available for a sensor’s +bands, each band’s FWHM is assumed to reach half the distance the its adjacent +bands. Resampling results are better when source bands are at a higher spectral +resolution than the destination bands.

+

To create BandResampler object, we can either pass it a +BandResampler object for each sensor or +a list of band centers and, optionally, a list of FWHM values. Once the +BandResampler is created, we can call it with a source sensor spectrum and it will +return the resampled spectrum.

+
In [63]: import spectral.io.aviris as aviris
+
+In [64]: img1 = aviris.open('f970619t01p02_r02_sc04.a.rfl', 'f970619t01p02_r02.a.spc')
+
+In [65]: bands2 = aviris.read_aviris_bands('92AV3C.spc')
+
+In [66]: resample = BandResampler(img1.bands, bands2)
+
+In [67]: x1 = img1[96, 304]
+
+In [68]: x2 = resample(x1)
+
+
+
+
+

NDVI

+

The Normalized Difference Vegetation Index (NDVI) is an indicator of the presence +of vegetation. The index is commonly defined as

+
+

NDVI = \frac{NIR-RED}{NIR+RED}

+

where NIR is the reflectance in the near infrared (NIR) part of the spectrum and +RED is the reflectance of the red band. For our sample image, if we take +band 21 (607.0 nm) for our red band and band 43 (802.5 nm) for near infrared, +we get the following NDVI image.

+
In [69]: vi = ndvi(img, 21, 43)
+
+In [70]: v = imshow(vi)
+
+
+_images/ndvi.png +

ndvi is a simple convenience function. +You could just as easily calculate the vegetation index yourself like this:

+
In [71]: red = img.read_band(21)
+
+In [72]: nir = img.read_band(43)
+
+In [73]: vi = (nir - red) / (nir + red)
+
+
+
+
+

Spectral Angles

+

A spectral angle refers to the angle between to spectra in N-space. In the +absence of covariance data, spectral angles can be used for classifying data +against a set of reference spectra by selecting the reference spectrum with +which the unknown spectrum has the smallest angle.

+

To classify our sample image to the ground truth classes using spectral angles, +we must compute the spectral angles for each pixel with each training class mean. +This is done by the spectral_angles function. +Before calling the function, we must first create a CxB array of training class +mean spectra, where C is the number of training classes and B is the number +of spectral bands.

+
In [74]: import numpy as np
+
+In [75]: classes = create_training_classes(img, gt, True)
+
+In [76]: means = np.zeros((len(classes), img.shape[2]), float)
+
+In [77]: for (i, c) in enumerate(classes):
+   ....:     means[i] = c.stats.mean
+   ....: 
+
+
+

In the code above, the True parameter to +create_training_classes forces calculation +of statistics for each class. Next, we call spectral_angles, +which returns an MxNxC array, where M and N are the number of rows and +columns in the image and there are C spectral angle for each pixel. To select +the class with the smallest angle, we call the numpy argmin +function to select the index for the smallest angle corresponding to each pixel. +The clmap + 1 is used in the display command because our class IDs start at 1 (not 0).

+
In [78]: angles = spectral_angles(img, means)
+
+In [79]: clmap = np.argmin(angles, 2)
+
+In [80]: v = imshow(classes=((clmap + 1) * (gt != 0)))
+
+
+_images/sam.png +
+

See also

+

msam: Modified Spectral Angle Mapper

+
+

References

+
+
Richards1999
+

Richards, J.A. & Jia, X. Remote Sensing Digital Image Analysis: An Introduction. (Springer: Berlin, 1999).

+
+
Reed_Yu_1990
+

Reed, I.S. and Yu, X., “Adaptive multiple-band CFAR detection of an optical pattern with unknown spectral distribution,” IEEE Trans. Acoust., Speech, Signal Processing, vol. 38, pp. 1760-1770, Oct. 1990.

+
+
+
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/class_func_glossary.html b/class_func_glossary.html new file mode 100644 index 0000000..3ac969c --- /dev/null +++ b/class_func_glossary.html @@ -0,0 +1,434 @@ + + + + + + + Class/Function Glossary — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+

Class/Function Glossary

+
+

File Input/Output

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

EcostressDatabase

Create/Query a spectral database generated from the ECOSTRESS Spectral Library

aviris.open

Open an AVIRIS image file

aviris.read_aviris_bands

Read an AVIRIS band calibration file

envi.open

Open a data file (image, classification, or spectral library) that has +an ENVI header

envi.create_image

Open a new (empty) image file with an ENVI header

envi.save_classification

Save classification labels to a file with a corresponding ENVI header

envi.save_image

Save image data to a file with a corresponding ENVI header

envi.SpectralLibrary

Class to create/save spectral libraries in ENVI format

erdas.open

Open an Erdas LAN image file

open_image

Generic function for opening multiple hyperspectral image file formats

+
+

+
+
+
+

Display/Visualization

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

ColorScale

Create color scales for use with data display functions

get_rgb

Produce RGB array suitable for display from image data

ImageView

Class for interacting with image displays (returned by imshow)

imshow

Primary function for displaying raster views of image data (an extension of matplotlib’s imshow providing overlays and interactivity)

ppi

Display Pixel Purity Index values while ppi function executes.

save_rgb

Saves image data in common RGB image formats (e.g., JPEG, PNG)

view_cube

View an interactive 3D image cube

view_nd

Display an interactive N-D visualization of image pixel data

+
+

+
+
+
+

Dimensionality Reduction

+ ++++ + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

linear_discriminant

Computes Fisher’s Linear Discriminant for a set of classes

mnf

Minimum Noise Fraction

ppi

Calculates Pixel Purity Index (PPI)

principal_components

Calculates principal components of an image

+
+

+
+
+
+

Target Detection

+ ++++ + + + + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

ace

Adaptive Coherence/Cosine Estimator

matched_filter

Applies a linear matched filter detector for a given target

msam

Modified Spectral Angle Mapper (MSAM)

rx

RX anomaly detector

spectral_angles

Spectral Angle Mapper (SAM)

+
+

+
+
+
+

Classification

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

create_training_classes

Creates a TrainingClassSet object from image data and ground truth

GaussianClassifier

Gaussian Maximum Likelihood Classifier (GMLC)

kmeans

Unsupervised image classification via k-means clustering

MahalanobisDistanceClassifier

A classifier using Mahalanobis distance

map_class_ids

Create a mapping between class labels in two classification images

map_classes

Modifies class indices according to a class index mapping

msam

Modified Spectral Angle Mapper (MSAM)

PerceptronClassifier

A Multi-Layer Perceptron (MLP) Artificial Neural Network (ANN) classifier

spectral_angles

Spectral Angle Mapper (SAM)

TrainingClassSet

Object returned from spectral.create_training_classes +(used by some classifiers)

+
+

+
+
+
+

Spectral Transforms

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

BandInfo

Container for spectral band discretization/calibration data

BandResampler

Class for performing band resampling

FisherLinearDiscriminant

Object returned by linear_discriminant to transform image data into linear discriminant space.

LinearTransform

A callable linear transform that can be applied to image data

MNFResult

Object returned by mnf to reduce dimensionality via Minimum Noise Fraction (MNF)

ndvi

Normalized Difference Vegetation Index (NDVI)

PrincipalComponents

Object returned by principal_components. Transforms data into PCA space and - optionally - reduces dimensionality.

+
+

+
+
+
+

Math/Statistics

+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Class/Function

Description

bdist

Bhattacharyya distance

calc_stats

Calculates Gaussian statistics for image data

covariance

Calculates image data covariance

cov_avg

Calculates covariance averaged over a set of ground truth classes

noise_from_diffs

Estimate image noise statistics from a homogeneous region

orthogonalize

Performs Gram-Schmidt orthogonalization on a set of vectors

principal_components

Calculates principal components of an image

transform_image

Applies a linear transform to image data

+
+
+

Miscellaneous

+ ++++ + + + + + + + + + + + + + +

Class/Function

Description

settings

Object for controlling SPy configuration options

algorithms.algorithms.iterator

Function returning an iterator over image pixels

+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/class_func_ref.html b/class_func_ref.html new file mode 100644 index 0000000..9ff479a --- /dev/null +++ b/class_func_ref.html @@ -0,0 +1,4536 @@ + + + + + + + Class/Function Documentation — Spectral Python 0.21 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+

Class/Function Documentation

+
+

File I/O

+
+

open_image

+
+
+open_image(file)
+

Locates & opens the specified hyperspectral image.

+

Arguments:

+
+
+
file (str):

Name of the file to open.

+
+
+
+

Returns:

+
+

SpyFile object to access the file.

+
+

Raises:

+
+

IOError.

+
+

This function attempts to determine the associated file type and open the +file. If the specified file is not found in the current directory, all +directories listed in the SPECTRAL_DATA environment variable will +be searched until the file is found. If the file being opened is an ENVI +file, the file argument should be the name of the header file.

+
+ +
+
+

ImageArray

+
+
+class ImageArray(data, spyfile)
+

ImageArray is an interface to an image loaded entirely into memory. +ImageArray objects are returned by spectral.SpyFile.load. +This class inherits from both numpy.ndarray and Image, providing the +interfaces of both classes.

+
+ +
+
+

SpyFile

+
+
+class SpyFile(params, metadata=None)
+

A base class for accessing spectral image files

+
+
+__getitem__(args)
+

Subscripting operator that provides a numpy-like interface. +Usage:

+
x = img[i, j]
+x = img[i, j, k]
+
+
+

Arguments:

+
+

i, j, k (int or slice object)

+
+

Integer subscript indices or slice objects.

+
+
+

The subscript operator emulates the numpy.ndarray subscript +operator, except data are read from the corresponding image file +instead of an array object in memory. For frequent access or when +accessing a large fraction of the image data, consider calling +spectral.SpyFile.load to load the data into an +spectral.image.ImageArray object and using its subscript operator +instead.

+

Examples:

+
+

Read the pixel at the 30th row and 51st column of the image:

+
pixel = img[29, 50]
+
+
+

Read the 10th band:

+
band = img[:, :, 9]
+
+
+

Read the first 30 bands for a square sub-region of the image:

+
region = img[50:100, 50:100, :30]
+
+
+
+
+ +
+
+__str__()
+

Prints basic parameters of the associated file.

+
+ +
+
+load(**kwargs)
+

Loads entire image into memory in a spectral.image.ImageArray.

+

Keyword Arguments:

+
+

dtype (numpy.dtype):

+
+

An optional dtype to which the loaded array should be cast.

+
+

scale (bool, default True):

+
+

Specifies whether any applicable scale factor should be applied +to the data after loading.

+
+
+

spectral.image.ImageArray is derived from both +spectral.image.Image and numpy.ndarray so it supports the +full numpy.ndarray interface. The returns object will have +shape (M,N,B), where M, N, and B are the numbers of rows, +columns, and bands in the image.

+
+ +
+ +
+

SpyFile Subclasses

+

SpyFile is an abstract base class. Subclasses of SpyFile +(BipFile, BilFile, +BsqFile) all implement a common set of additional +methods. BipFile is shown here but the other two are similar.

+
+
+class BipFile(params, metadata=None)
+

A class to interface image files stored with bands interleaved by pixel.

+
+
+open_memmap(**kwargs)
+

Returns a new numpy.memmap object for image file data access.

+

Keyword Arguments:

+
+

interleave (str, default ‘bip’):

+
+

Specifies the shape/interleave of the returned object. Must be +one of [‘bip’, ‘bil’, ‘bsq’, ‘source’]. If not specified, the +memmap will be returned as ‘bip’. If the interleave is +‘source’, the interleave of the memmap will be the same as the +source data file. If the number of rows, columns, and bands in +the file are R, C, and B, the shape of the returned memmap +array will be as follows:

+ ++++ + + + + + + + + + + + + + + + + +

interleave

array shape

‘bip’

(R, C, B)

‘bil’

(R, B, C)

‘bsq’

(B, R, C)

+
+

writable (bool, default False):

+
+

If writable is True, modifying values in the returned memmap +will result in corresponding modification to the image data +file.

+
+
+
+ +
+
+read_band(band, use_memmap=True)
+

Reads a single band from the image.

+

Arguments:

+
+

band (int):

+
+

Index of band to read.

+
+

use_memmap (bool, default True):

+
+

Specifies whether the file’s memmap interface should be used +to read the data. Setting this arg to True only has an effect +if a memmap is being used (i.e., if img.using_memmap is True).

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxN array of values for the specified band.

+
+
+
+ +
+
+read_bands(bands, use_memmap=True)
+

Reads multiple bands from the image.

+

Arguments:

+
+

bands (list of ints):

+
+

Indices of bands to read.

+
+

use_memmap (bool, default True):

+
+

Specifies whether the file’s memmap interface should be used +to read the data. Setting this arg to True only has an effect +if a memmap is being used (i.e., if img.using_memmap is True).

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxNxL array of values for the specified bands. M and N +are the number of rows & columns in the image and L equals +len(bands).

+
+
+
+ +
+
+read_pixel(row, col, use_memmap=True)
+

Reads the pixel at position (row,col) from the file.

+

Arguments:

+
+

row, col (int):

+
+

Indices of the row & column for the pixel

+
+

use_memmap (bool, default True):

+
+

Specifies whether the file’s memmap interface should be used +to read the data. Setting this arg to True only has an effect +if a memmap is being used (i.e., if img.using_memmap is True).

+
+
+

Returns:

+
+

numpy.ndarray

+
+

A length-B array, where B is the number of image bands.

+
+
+
+ +
+
+read_subimage(rows, cols, bands=None, use_memmap=False)
+

Reads arbitrary rows, columns, and bands from the image.

+

Arguments:

+
+

rows (list of ints):

+
+

Indices of rows to read.

+
+

cols (list of ints):

+
+

Indices of columns to read.

+
+

bands (list of ints):

+
+

Optional list of bands to read. If not specified, all bands +are read.

+
+

use_memmap (bool, default False):

+
+

Specifies whether the file’s memmap interface should be used +to read the data. Setting this arg to True only has an effect +if a memmap is being used (i.e., if img.using_memmap is True).

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxNxL array, where M = len(rows), N = len(cols), +and L = len(bands) (or # of image bands if bands == None).

+
+
+
+ +
+
+read_subregion(row_bounds, col_bounds, bands=None, use_memmap=True)
+

Reads a contiguous rectangular sub-region from the image.

+

Arguments:

+
+

row_bounds (2-tuple of ints):

+
+

(a, b) -> Rows a through b-1 will be read.

+
+

col_bounds (2-tuple of ints):

+
+

(a, b) -> Columnss a through b-1 will be read.

+
+

bands (list of ints):

+
+

Optional list of bands to read. If not specified, all bands +are read.

+
+

use_memmap (bool, default True):

+
+

Specifies whether the file’s memmap interface should be used +to read the data. Setting this arg to True only has an effect +if a memmap is being used (i.e., if img.using_memmap is True).

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxNxL array.

+
+
+
+ +
+ +
+
+
+

SubImage

+
+
+class SubImage(image, row_range, col_range)
+

Represents a rectangular sub-region of a larger SpyFile object.

+
+
+read_band(band)
+

Reads a single band from the image.

+

Arguments:

+
+

band (int):

+
+

Index of band to read.

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxN array of values for the specified band.

+
+
+
+ +
+
+read_bands(bands)
+

Reads multiple bands from the image.

+

Arguments:

+
+

bands (list of ints):

+
+

Indices of bands to read.

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxNxL array of values for the specified bands. M and N +are the number of rows & columns in the image and L equals +len(bands).

+
+
+
+ +
+
+read_pixel(row, col)
+

Reads the pixel at position (row,col) from the file.

+

Arguments:

+
+

row, col (int):

+
+

Indices of the row & column for the pixel

+
+
+

Returns:

+
+

numpy.ndarray

+
+

A length-B array, where B is the number of image bands.

+
+
+
+ +
+
+read_subimage(rows, cols, bands=[])
+

Reads arbitrary rows, columns, and bands from the image.

+

Arguments:

+
+

rows (list of ints):

+
+

Indices of rows to read.

+
+

cols (list of ints):

+
+

Indices of columns to read.

+
+

bands (list of ints):

+
+

Optional list of bands to read. If not specified, all bands +are read.

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxNxL array, where M = len(rows), N = len(cols), +and L = len(bands) (or # of image bands if bands == None).

+
+
+
+ +
+
+read_subregion(row_bounds, col_bounds, bands=None)
+

Reads a contiguous rectangular sub-region from the image.

+

Arguments:

+
+

row_bounds (2-tuple of ints):

+
+

(a, b) -> Rows a through b-1 will be read.

+
+

col_bounds (2-tuple of ints):

+
+

(a, b) -> Columnss a through b-1 will be read.

+
+

bands (list of ints):

+
+

Optional list of bands to read. If not specified, all bands +are read.

+
+
+

Returns:

+
+

numpy.ndarray

+
+

An MxNxL array.

+
+
+
+ +
+ +
+
+

File Formats

+
+

AVIRIS

+

Functions for handling AVIRIS image files.

+
+
+open(file, band_file=None)
+

Returns a SpyFile object for an AVIRIS image file.

+

Arguments:

+
+

file (str):

+
+

Name of the AVIRIS data file.

+
+

band_file (str):

+
+

Optional name of the AVIRIS spectral calibration file.

+
+
+

Returns:

+
+

A SpyFile object for the image file.

+
+

Raises:

+
+

spectral.io.spyfile.InvalidFileError

+
+
+ +
+
+read_aviris_bands(cal_filename)
+

Returns a BandInfo object for an AVIRIS spectral calibration file.

+

Arguments:

+
+

cal_filename (str):

+
+

Name of the AVIRIS spectral calibration file.

+
+
+

Returns:

+
+
+
+ +
+
+

ENVI

+

ENVI 1 is a popular commercial software package for processing +and analyzing geospatial imagery. SPy supports reading imagery with associated +ENVI header files and reading & writing spectral libraries with ENVI headers. +ENVI files are opened automatically by the SPy image function +but can also be called explicitly. It may be necessary to open an ENVI file +explicitly if the data file is in a separate directory from the header or if +the data file has an unusual file extension that SPy can not identify.

+
>>> import spectral.io.envi as envi
+>>> img = envi.open('cup95eff.int.hdr', '/Users/thomas/spectral_data/cup95eff.int')
+
+
+
+
1
+

ENVI is a registered trademark of Exelis, Inc.

+
+
+
+
+open(file, image=None)
+

Opens an image or spectral library with an associated ENVI HDR header file.

+

Arguments:

+
+

file (str):

+
+

Name of the header file for the image.

+
+

image (str):

+
+

Optional name of the associated image data file.

+
+
+

Returns:

+
+
+

Raises:

+
+

TypeError, EnviDataFileNotFoundError

+
+

If the specified file is not found in the current directory, all +directories listed in the SPECTRAL_DATA environment variable will be +searched until the file is found. Based on the name of the header file, +this function will search for the image file in the same directory as the +header, looking for a file with the same name as the header but different +extension. Extensions recognized are .img, .dat, .sli, and no extension. +Capitalized versions of the file extensions are also searched.

+
+ +
+
+class SpectralLibrary(data, header=None, params=None)
+

The envi.SpectralLibrary class holds data contained in an ENVI-formatted +spectral library file (.sli files), which stores data as specified by a +corresponding .hdr header file. The primary members of an +Envi.SpectralLibrary object are:

+
+

spectra (numpy.ndarray):

+
+

A subscriptable array of all spectra in the library. spectra will +have shape CxB, where C is the number of spectra in the library +and B is the number of bands for each spectrum.

+
+

names (list of str):

+
+

A length-C list of names corresponding to the spectra.

+
+

bands (spectral.BandInfo):

+
+

Spectral bands associated with the library spectra.

+
+
+
+
+save(file_basename, description=None)
+

Saves the spectral library to a library file.

+

Arguments:

+
+

file_basename (str):

+
+

Name of the file (without extension) to save.

+
+

description (str):

+
+

Optional text description of the library.

+
+
+

This method creates two files: file_basename.hdr and +file_basename.sli.

+
+ +
+ +
+
+
+

envi.create_image

+
+
+create_image(hdr_file, metadata=None, **kwargs)
+

Creates an image file and ENVI header with a memmep array for write access.

+

Arguments:

+
+

hdr_file (str):

+
+

Header file (with “.hdr” extension) name with path.

+
+

metadata (dict):

+
+

Metadata to specify the image file format. The following parameters +(in ENVI header format) are required, if not specified via +corresponding keyword arguments: “bands”, “lines”, “samples”, +and “data type”.

+
+
+

Keyword Arguments:

+
+

dtype (numpy dtype or type string):

+
+

The numpy data type with which to store the image. For example, +to store the image in 16-bit unsigned integer format, the argument +could be any of numpy.uint16, “u2”, “uint16”, or “H”. If this +keyword is given, it will override the “data type” parameter in +the metadata argument.

+
+

force (bool, False by default):

+
+

If the associated image file or header already exist and force is +True, the files will be overwritten; otherwise, if either of the +files exist, an exception will be raised.

+
+

ext (str):

+
+

The extension to use for the image file. If not specified, the +default extension “.img” will be used. If ext is an empty +string, the image file will have the same name as the header but +without the “.hdr” extension.

+
+

interleave (str):

+
+

Must be one of “bil”, “bip”, or “bsq”. This keyword supercedes the +value of “interleave” in the metadata argument, if given. If no +interleave is specified (via keyword or metadata), “bip” is +assumed.

+
+

shape (tuple of integers):

+
+

Specifies the number of rows, columns, and bands in the image. +This keyword should be either of the form (R, C, B) or (R, C), +where R, C, and B specify the number or rows, columns, and bands, +respectively. If B is omitted, the number of bands is assumed to +be one. If this keyword is given, its values supercede the values +of “bands”, “lines”, and “samples” if they are present in the +metadata argument.

+
+

offset (integer, default 0):

+
+

The offset (in bytes) of image data from the beginning of the file. +This value supercedes the value of “header offset” in the metadata +argument (if given).

+
+
+

Returns:

+
+

SpyFile object:

+
+

To access a numpy.memmap for the returned SpyFile object, call +the open_memmap method of the returned object.

+
+
+

Examples:

+
+

Creating a new image from metadata:

+
>>> md = {'lines': 30,
+          'samples': 40,
+          'bands': 50,
+          'data type': 12}
+>>> img = envi.create_image('new_image.hdr', md)
+
+
+

Creating a new image via keywords:

+
>>> img = envi.create_image('new_image2.hdr',
+                            shape=(30, 40, 50),
+                            dtype=np.uint16)
+
+
+

Writing to the new image using a memmap interface:

+
>>> # Set all band values for a single pixel to 100.
+>>> mm = img.open_memmap(writable=True)
+>>> mm[30, 30] = 100
+
+
+
+
+ +
+
+

envi.save_classification

+
+
+save_classification(hdr_file, image, **kwargs)
+

Saves a classification image to disk.

+

Arguments:

+
+

hdr_file (str):

+
+

Header file (with “.hdr” extension) name with path.

+
+

image (SpyFile object or numpy.ndarray):

+
+

The image to save.

+
+
+

Keyword Arguments:

+
+

dtype (numpy dtype or type string):

+
+

The numpy data type with which to store the image. For example, +to store the image in 16-bit unsigned integer format, the argument +could be any of numpy.uint16, “u2”, “uint16”, or “H”.

+
+

force (bool):

+
+

If the associated image file or header already exist and force is +True, the files will be overwritten; otherwise, if either of the +files exist, an exception will be raised.

+
+

ext (str):

+
+

The extension to use for the image file. If not specified, the +default extension “.img” will be used. If ext is an empty +string, the image file will have the same name as the header but +without the “.hdr” extension.

+
+

interleave (str):

+
+

The band interleave format to use in the file. This argument +should be one of “bil”, “bip”, or “bsq”. If not specified, the +image will be written in BIP interleave.

+
+

byteorder (int or string):

+
+

Specifies the byte order (endian-ness) of the data as +written to disk. For little endian, this value should be +either 0 or “little”. For big endian, it should be +either 1 or “big”. If not specified, native byte order +will be used.

+
+

metadata (dict):

+
+

A dict containing ENVI header parameters (e.g., parameters +extracted from a source image).

+
+

class_names (array of strings):

+
+

For classification results, specifies the names to assign each +integer in the class map being written. If not given, default +class names are created.

+
+

class_colors (array of RGB-tuples):

+
+

For classification results, specifies colors to assign each +integer in the class map being written. If not given, default +colors are automatically generated.

+
+
+

If the source image being saved was already in ENVI format, then the +SpyFile object for that image will contain a metadata dict that can be +passed as the metadata keyword. However, care should be taken to ensure +that all the metadata fields from the source image are still accurate +(e.g., wavelengths do not apply to classification results).

+
+ +
+
+

envi.save_image

+
+
+save_image(hdr_file, image, **kwargs)
+

Saves an image to disk.

+

Arguments:

+
+

hdr_file (str):

+
+

Header file (with “.hdr” extension) name with path.

+
+

image (SpyFile object or numpy.ndarray):

+
+

The image to save.

+
+
+

Keyword Arguments:

+
+

dtype (numpy dtype or type string):

+
+

The numpy data type with which to store the image. For example, +to store the image in 16-bit unsigned integer format, the argument +could be any of numpy.uint16, “u2”, “uint16”, or “H”.

+
+

force (bool):

+
+

If the associated image file or header already exist and force is +True, the files will be overwritten; otherwise, if either of the +files exist, an exception will be raised.

+
+

ext (str or None):

+
+

The extension to use for the image file. If not specified, the +default extension “.img” will be used. If ext is an empty +string or is None, the image file will have the same name as the +header but without the “.hdr” extension.

+
+

interleave (str):

+
+

The band interleave format to use in the file. This argument +should be one of “bil”, “bip”, or “bsq”. If not specified, the +image will be written in BIP interleave.

+
+

byteorder (int or string):

+
+

Specifies the byte order (endian-ness) of the data as +written to disk. For little endian, this value should be +either 0 or “little”. For big endian, it should be +either 1 or “big”. If not specified, native byte order +will be used.

+
+

metadata (dict):

+
+

A dict containing ENVI header parameters (e.g., parameters +extracted from a source image).

+
+
+

Example:

+
>>> # Save the first 10 principal components of an image
+>>> data = open_image('92AV3C.lan').load()
+>>> pc = principal_components(data)
+>>> pcdata = pc.reduce(num=10).transform(data)
+>>> envi.save_image('pcimage.hdr', pcdata, dtype=np.float32)
+
+
+

If the source image being saved was already in ENVI format, then the +SpyFile object for that image will contain a metadata dict that can be +passed as the metadata keyword. However, care should be taken to ensure +that all the metadata fields from the source image are still accurate +(e.g., band names or wavelengths will no longer be correct if the data +being saved are from a principal components transformation).

+
+ +
+

ERDAS/Lan

+
+
+
+

erdas.open

+

Functions for reading Erdas files.

+
+
+open(file)
+

Returns a SpyFile object for an ERDAS/Lan image file.

+

Arguments:

+
+

file (str):

+
+

Name of the ERDAS/Lan image data file.

+
+
+

Returns:

+
+

A SpyFile object for the image file.

+
+

Raises:

+
+

spectral.io.spyfile.InvalidFileError

+
+
+ +
+
+
+

Graphics

+
+

ColorScale

+
+
+class ColorScale(levels, colors, num_tics=0)
+

A color scale class to map scalar values to rgb colors. The class allows +associating colors with particular scalar values, setting a background +color (for values below threshold), andadjusting the scale limits. The +__call__ operator takes a scalar input and returns the +corresponding color, interpolating between defined colors.

+
+
+__call__(val)
+

Returns the scale color associated with the given value.

+
+ +
+
+__init__(levels, colors, num_tics=0)
+

Creates the ColorScale.

+

Arguments:

+
+

levels (list of numbers):

+
+

Scalar levels to which the colors argument will correspond.

+
+

colors (list of 3-tuples):

+
+

RGB 3-tuples that define the colors corresponding to levels.

+
+

num_tics (int):

+
+

The total number of colors in the scale, not including the +background color. This includes the colors given in the +colors argument, as well as interpolated color values. If +not specified, only the colors in the colors argument will +be used (i.e., num_tics = len(colors).

+
+
+
+ +
+
+set_background_color()
+

Sets RGB color used for values below the scale minimum.

+

Arguments:

+
+

color (3-tuple): An RGB triplet

+
+
+ +
+
+set_range(min, max)
+

Sets the min and max values of the color scale.

+

The distribution of colors within the scale will stretch or shrink +accordingly.

+
+ +
+ +
+
+

get_rgb

+
+
+get_rgb(source, bands=None, **kwargs)
+

Extract RGB data for display from a SpyFile object or numpy array.

+
+
USAGE: rgb = get_rgb(source [, bands] [, stretch=<arg> | , bounds=<arg>]

[, stretch_all=<arg>])

+
+
+

Arguments:

+
+

source (spectral.SpyFile or numpy.ndarray):

+
+

Data source from which to extract the RGB data.

+
+

bands (list of int) (optional):

+
+

Optional triplet of indices which specifies the bands to extract +for the red, green, and blue components, respectively. If this +arg is not given, SpyFile object, it’s metadata dict will be +checked to see if it contains a “default bands” item. If it does +not, then first, middle and last band will be returned.

+
+
+

Keyword Arguments:

+
+

stretch (numeric or tuple):

+
+

This keyword specifies two points on the cumulative histogram of +the input data for performing a linear stretch of RGB value for the +data. Numeric values given for this parameter are expected to be +between 0 and 1. This keyword can be expressed in three forms:

+
    +
  1. As a 2-tuple. In this case the two values specify the lower and +upper points of the cumulative histogram respectively. The +specified stretch will be performed independently on each of the +three color channels unless the stretch_all keyword is set to +True, in which case all three color channels will be stretched +identically.

  2. +
  3. As a 3-tuple of 2-tuples. In this case, Each channel will be +stretched according to its respective 2-tuple in the keyword +argument.

  4. +
  5. As a single numeric value. In this case, the value indicates the +size of the histogram tail to be applied at both ends of the +histogram for each color channel. stretch=a is equivalent to +stretch=(a, 1-a).

  6. +
+

If neither stretch nor bounds are specified, then the default +value of stretch defined by spectral.settings.imshow_stretch +will be used.

+
+

bounds (tuple):

+
+

This keyword functions similarly to the stretch keyword, except +numeric values are in image data units instead of cumulative +histogram values. The form of this keyword is the same as the first +two forms for the stretch keyword (i.e., either a 2-tuple of +numbers or a 3-tuple of 2-tuples of numbers).

+
+

stretch_all (bool):

+
+

If this keyword is True, each color channel will be scaled +independently.

+
+

color_scale (ColorScale):

+
+

A color scale to be applied to a single-band image.

+
+

auto_scale (bool):

+
+

If color_scale is provided and auto_scale is True, the min/max +values of the color scale will be mapped to the min/max data +values.

+
+

colors (ndarray):

+
+

If source is a single-band integer-valued np.ndarray and this +keyword is provided, then elements of source are assumed to be +color index values that specify RGB values in colors.

+
+
+

Examples:

+

Select color limits corresponding to 2% tails in the data histogram:

+
>>> imshow(x, stretch=0.02)
+
+
+

Same as above but specify upper and lower limits explicitly:

+
>>> imshow(x, stretch=(0.02, 0.98))
+
+
+

Same as above but specify limits for each RGB channel explicitly:

+
>>> imshow(x, stretch=((0.02, 0.98), (0.02, 0.98), (0.02, 0.98)))
+
+
+
+ +
+
+

ImageView

+
+
+class ImageView(data=None, bands=None, classes=None, source=None, **kwargs)
+

Class to manage events and data associated with image raster views.

+

In most cases, it is more convenient to simply call imshow, +which creates, displays, and returns an ImageView object. Creating +an ImageView object directly (or creating an instance of a subclass) +enables additional customization of the image display (e.g., overriding +default event handlers). If the object is created directly, call the +show method to display the image. The underlying image display +functionality is implemented via matplotlib.pyplot.imshow.

+
+
+__init__(data=None, bands=None, classes=None, source=None, **kwargs)
+

Arguments:

+
+

data (ndarray or SpyFile):

+
+

The source of RGB bands to be displayed. with shape (R, C, B). +If the shape is (R, C, 3), the last dimension is assumed to +provide the red, green, and blue bands (unless the bands +argument is provided). If B > 3 and bands is not +provided, the first, middle, and last band will be used.

+
+

bands (triplet of integers):

+
+

Specifies which bands in data should be displayed as red, +green, and blue, respectively.

+
+

classes (ndarray of integers):

+
+

An array of integer-valued class labels with shape (R, C). If +the data argument is provided, the shape must match the first +two dimensions of data.

+
+

source (ndarray or SpyFile):

+
+

The source of spectral data associated with the image display. +This optional argument is used to access spectral data (e.g., to +generate a spectrum plot when a user double-clicks on the image +display.

+
+
+

Keyword arguments:

+
+

Any keyword that can be provided to get_rgb +or matplotlib.imshow.

+
+
+ +
+
+property class_alpha
+

alpha transparency for the class overlay.

+
+ +
+
+format_coord(x, y)
+

Formats pixel coordinate string displayed in the window.

+
+ +
+
+property interpolation
+

matplotlib pixel interpolation to use in the image display.

+
+ +
+
+label_region(rectangle, class_id)
+

Assigns all pixels in the rectangle to the specified class.

+

Arguments:

+
+

rectangle (4-tuple of integers):

+
+

Tuple or list defining the rectangle bounds. Should have the +form (row_start, row_stop, col_start, col_stop), where the +stop indices are not included (i.e., the effect is +classes[row_start:row_stop, col_start:col_stop] = id.

+
+

class_id (integer >= 0):

+
+

The class to which pixels will be assigned.

+
+
+

Returns the number of pixels reassigned (the number of pixels in the +rectangle whose class has changed to class_id.

+
+ +
+
+open_zoom(center=None, size=None)
+

Opens a separate window with a zoomed view. +If a ctrl-lclick event occurs in the original view, the zoomed window +will pan to the location of the click event.

+

Arguments:

+
+

center (two-tuple of int):

+
+

Initial (row, col) of the zoomed view.

+
+

size (int):

+
+

Width and height (in source image pixels) of the initial +zoomed view.

+
+
+

Returns:

+

A new ImageView object for the zoomed view.

+
+ +
+
+pan_to(row, col)
+

Centers view on pixel coordinate (row, col).

+
+ +
+
+refresh()
+

Updates the displayed data (if it has been shown).

+
+ +
+
+set_classes(classes, colors=None, **kwargs)
+

Sets the array of class values associated with the image data.

+

Arguments:

+
+

classes (ndarray of int):

+
+

classes must be an integer-valued array with the same +number rows and columns as the display data (if set).

+
+

colors: (array or 3-tuples):

+
+

Color triplets (with values in the range [0, 255]) that +define the colors to be associatd with the integer indices +in classes.

+
+
+

Keyword Arguments:

+
+

Any valid keyword for matplotlib.imshow can be provided.

+
+
+ +
+
+set_data(data, bands=None, **kwargs)
+

Sets the data to be shown in the RGB channels.

+

Arguments:

+
+

data (ndarray or SpyImage):

+
+

If data has more than 3 bands, the bands argument can be +used to specify which 3 bands to display. data will be +passed to get_rgb prior to display.

+
+

bands (3-tuple of int):

+
+

Indices of the 3 bands to display from data.

+
+
+

Keyword Arguments:

+
+

Any valid keyword for get_rgb or matplotlib.imshow can be +given.

+
+
+ +
+
+set_display_mode(mode)
+

mode must be one of (“data”, “classes”, “overlay”).

+
+ +
+
+set_rgb_options(**kwargs)
+

Sets parameters affecting RGB display of data.

+

Accepts any keyword supported by get_rgb.

+
+ +
+
+set_source(source)
+

Sets the image data source (used for accessing spectral data).

+

Arguments:

+
+

source (ndarray or SpyFile):

+
+

The source for spectral data associated with the view.

+
+
+
+ +
+
+show(mode=None, fignum=None)
+

Renders the image data.

+

Arguments:

+
+

mode (str):

+
+

Must be one of:

+
+

“data”: Show the data RGB

+

“classes”: Shows indexed color for classes

+

“overlay”: Shows class colors overlaid on data RGB.

+
+

If mode is not provided, a mode will be automatically +selected, based on the data set in the ImageView.

+
+

fignum (int):

+
+

Figure number of the matplotlib figure in which to display +the ImageView. If not provided, a new figure will be created.

+
+
+
+ +
+ +
+
+

imshow

+
+
+imshow(data=None, bands=None, classes=None, source=None, colors=None, figsize=None, fignum=None, title=None, **kwargs)
+

A wrapper around matplotlib’s imshow for multi-band images.

+

Arguments:

+
+

data (SpyFile or ndarray):

+
+

Can have shape (R, C) or (R, C, B).

+
+

bands (tuple of integers, optional)

+
+

If bands has 3 values, the bands specified are extracted from +data to be plotted as the red, green, and blue colors, +respectively. If it contains a single value, then a single band +will be extracted from the image.

+
+

classes (ndarray of integers):

+
+

An array of integer-valued class labels with shape (R, C). If +the data argument is provided, the shape must match the first +two dimensions of data. The returned ImageView object will use +a copy of this array. To access class values that were altered +after calling imshow, access the classes attribute of the +returned ImageView object.

+
+

source (optional, SpyImage or ndarray):

+
+

Object used for accessing image source data. If this argument is +not provided, events such as double-clicking will have no effect +(i.e., a spectral plot will not be created).

+
+

colors (optional, array of ints):

+
+

Custom colors to be used for class image view. If provided, this +argument should be an array of 3-element arrays, each of which +specifies an RGB triplet with integer color components in the +range [0, 256).

+
+

figsize (optional, 2-tuple of scalar):

+
+

Specifies the width and height (in inches) of the figure window +to be created. If this value is not provided, the value specified +in spectral.settings.imshow_figure_size will be used.

+
+

fignum (optional, integer):

+
+

Specifies the figure number of an existing matplotlib figure. If +this argument is None, a new figure will be created.

+
+

title (str):

+
+

The title to be displayed above the image.

+
+
+

Keywords:

+
+

Keywords accepted by get_rgb or +matplotlib.imshow will be passed on to the appropriate +function.

+
+

This function defaults the color scale (imshow’s “cmap” keyword) to +“gray”. To use imshow’s default color scale, call this function with +keyword cmap=None.

+

Returns:

+
+

An ImageView object, which can be subsequently used to refine the +image display.

+
+

See ImageView for additional details.

+

Examples:

+

Show a true color image of a hyperspectral image:

+
>>> data = open_image('92AV3C.lan').load()
+>>> view = imshow(data, bands=(30, 20, 10))
+
+
+

Show ground truth in a separate window:

+
>>> classes = open_image('92AV3GT.GIS').read_band(0)
+>>> cview = imshow(classes=classes)
+
+
+

Overlay ground truth data on the data display:

+
>>> view.set_classes(classes)
+>>> view.set_display_mode('overlay')
+
+
+

Show RX anomaly detector results in the view and a zoom window showing +true color data:

+
>>> x = rx(data)
+>>> zoom = view.open_zoom()
+>>> view.set_data(x)
+
+
+

Note that pressing ctrl-lclick with the mouse in the main window will +cause the zoom window to pan to the clicked location.

+

Opening zoom windows, changing display modes, and other functions can +also be achieved via keys mapped directly to the displayed image. Press +“h” with focus on the displayed image to print a summary of mouse/ +keyboard commands accepted by the display.

+
+ +
+
+

save_rgb

+
+
+save_rgb(filename, data, bands=None, **kwargs)
+

Saves a viewable image to a JPEG (or other format) file.

+

Usage:

+
save_rgb(filename, data, bands=None, **kwargs)
+
+
+

Arguments:

+
+

filename (str):

+
+

Name of image file to save (e.g. “rgb.jpg”)

+
+

data (spectral.Image or numpy.ndarray):

+
+

Source image data to display. data can be and instance of a +spectral.Image (e.g., spectral.SpyFile or +spectral.ImageArray) or a numpy.ndarray. data +must have shape MxN or MxNxB. If thes shape is MxN, the +image will be saved as greyscale (unless keyword colors is +specified). If the shape is MxNx3, it will be interpreted as +three MxN images defining the R, G, and B channels respectively. +If B > 3, the first, middle, and last images in data will be +used, unless bands is specified.

+
+

bands (3-tuple of ints):

+
+

Optional list of indices for bands to use in the red, green, +and blue channels, respectively.

+
+
+

Keyword Arguments:

+
+

format (str):

+
+

The image file format to create. Must be a format recognized by +PIL (e.g., ‘png’, ‘tiff’, ‘bmp’). If format is not +provided, ‘jpg’ is assumed.

+
+

See get_rgb for descriptions of +additional keyword arguments.

+
+

Examples:

+
+

Save a color view of an image by specifying RGB band indices:

+
save_image('rgb.jpg', img, [29, 19, 9]])
+
+
+

Save the same image as png:

+
save_image('rgb.png', img, [29, 19, 9]], format='png')
+
+
+

Save classification results using the default color palette (note that +the color palette must be passed explicitly for clMap to be +interpreted as a color map):

+
save_image('results.jpg', clMap, colors=spectral.spy_colors)
+
+
+
+
+ +
+
+

view

+
+
+view(*args, **kwargs)
+

Opens a window and displays a raster greyscale or color image.

+

Usage:

+
view(source, bands=None, **kwargs)
+
+
+

Arguments:

+
+

source (spectral.Image or numpy.ndarray):

+
+

Source image data to display. source can be and instance of a +spectral.Image (e.g., spectral.SpyFile or +spectral.ImageArray) or a numpy.ndarray. source +must have shape MxN or MxNxB.

+
+

bands (3-tuple of ints):

+
+

Optional list of indices for bands to display in the red, green, +and blue channels, respectively.

+
+
+

Keyword Arguments:

+
+

stretch (bool):

+
+

If stretch evaluates True, the highest value in the data source +will be scaled to maximum color channel intensity.

+
+

stretch_all (bool):

+
+

If stretch_all evaluates True, the highest value of the data +source in each color channel will be set to maximum intensity.

+
+

bounds (2-tuple of ints):

+
+

Clips the input data at (lower, upper) values.

+
+

title (str):

+
+

Text to display in the new window frame.

+
+
+

source is the data source and can be either a spectral.Image +object or a numpy array. If source has shape MxN, the image will be +displayed in greyscale. If its shape is MxNx3, the three layers/bands +will be displayed as the red, green, and blue components of the displayed +image, respectively. If its shape is MxNxB, where B > 3, the first, +middle, and last bands will be displayed in the RGB channels, unless +bands is specified.

+
+ +
+
+

view_cube

+
+
+view_cube(data, *args, **kwargs)
+

Renders an interactive 3D hypercube in a new window.

+

Arguments:

+
+

data (spectral.Image or numpy.ndarray):

+
+

Source image data to display. data can be and instance of a +spectral.Image (e.g., spectral.SpyFile or +spectral.ImageArray) or a numpy.ndarray. source +must have shape MxN or MxNxB.

+
+
+

Keyword Arguments:

+
+

bands (3-tuple of ints):

+
+

3-tuple specifying which bands from the image data should be +displayed on top of the cube.

+
+

top (numpy.ndarray or PIL.Image):

+
+

Data to display on top of the cube. This will supercede the +bands keyword.

+
+

scale (spectral.ColorScale)

+
+

A color scale to be used for color in the sides of the cube. If +this keyword is not specified, +spectral.graphics.colorscale.defaultColorScale is used.

+
+

size (2-tuple of ints):

+
+

Width and height (in pixels) for initial size of the new window.

+
+

background (3-tuple of floats):

+
+

Background RGB color of the scene. Each value should be in the +range [0, 1]. If not specified, the background will be black.

+
+

title (str):

+
+

Title text to display in the new window frame.

+
+
+

This function opens a new window, renders a 3D hypercube, and accepts +keyboard input to manipulate the view of the hypercube. Accepted keyboard +inputs are printed to the console output. Focus must be on the 3D window +to accept keyboard input.

+
+ +
+
+

view_indexed

+
+
+view_indexed(*args, **kwargs)
+

Opens a window and displays a raster image for the provided color map data.

+

Usage:

+
view_indexed(data, **kwargs)
+
+
+

Arguments:

+
+

data (numpy.ndarray):

+
+

An MxN array of integer values that correspond to colors in a +color palette.

+
+
+

Keyword Arguments:

+
+

colors (list of 3-tuples of ints):

+
+

This parameter provides an alternate color map to use for display. +The parameter is a list of 3-tuples defining RGB values, where R, +G, and B are in the range [0-255].

+
+

title (str):

+
+

Text to display in the new window frame.

+
+
+

The default color palette used is defined by spectral.spy_colors.

+
+ +
+
+

view_nd

+
+
+view_nd(data, *args, **kwargs)
+

Creates a 3D window that displays ND data from an image.

+

Arguments:

+
+

data (spectral.ImageArray or numpy.ndarray):

+
+

Source image data to display. data can be and instance of a +spectral.ImageArray or a :class:`numpy.ndarray. source +must have shape MxNxB, where M >= 3.

+
+
+

Keyword Arguments:

+
+

classes (numpy.ndarray):

+
+

2-dimensional array of integers specifying the classes of each +pixel in data. classes must have the same dimensions as the +first two dimensions of data.

+
+

features (list or list of integer lists):

+
+

This keyword specifies which bands/features from data should be +displayed in the 3D window. It must be defined as one of the +following:

+
    +
  1. A length-3 list of integer feature IDs. In this case, the data +points will be displayed in the positive x,y,z octant using +features associated with the 3 integers.

  2. +
  3. A length-6 list of integer feature IDs. In this case, each +integer specifies a single feature index to be associated with +the coordinate semi-axes x, y, z, -x, -y, and -z (in that +order). Each octant will display data points using the features +associated with the 3 semi-axes for that octant.

  4. +
  5. A length-8 list of length-3 lists of integers. In this case, +each length-3 list specfies the features to be displayed in a +single octants (the same semi-axis can be associated with +different features in different octants). Octants are ordered +starting with the postive x,y,z octant and procede +counterclockwise around the z-axis, then procede similarly +around the negative half of the z-axis. An octant triplet can +be specified as None instead of a list, in which case nothing +will be rendered in that octant.

  6. +
+
+

labels (list):

+
+

List of labels to be displayed next to the axis assigned to a +feature. If not specified, the feature index is shown by default.

+

The str() function will be called on each item of the list so, +for example, a list of wavelengths can be passed as the labels.

+
+

size (2-tuple of ints)

+
+

Specifies the initial size (pixel rows/cols) of the window.

+
+

title (string)

+
+

The title to display in the ND window title bar.

+
+
+

Returns an NDWindowProxy object with a classes member to access the +current class labels associated with data points and a set_features +member to specify which features are displayed.

+
+ +
+
+
+

Training Classes

+
+

create_training_classes

+
+
+create_training_classes(image, class_mask, calc_stats=False, indices=None)
+

Creates a :class:spectral.algorithms.TrainingClassSet: from an indexed array.

+

USAGE: sets = createTrainingClasses(classMask)

+

Arguments:

+
+

image (spectral.Image or numpy.ndarray):

+
+

The image data for which the training classes will be defined. +image has shape MxNxB.

+
+

class_mask (numpy.ndarray):

+
+

A rank-2 array whose elements are indices of various spectral +classes. if class_mask[i,j] == k, then image[i,j] is +assumed to belong to class k.

+
+

calc_stats (bool):

+
+

An optional parameter which, if True, causes statistics to be +calculated for all training classes.

+
+
+

Returns:

+
+

A spectral.algorithms.TrainingClassSet object.

+
+

The dimensions of classMask should be the same as the first two dimensions +of the corresponding image. Values of zero in classMask are considered +unlabeled and are not added to a training set.

+
+ +
+
+

TrainingClass

+
+
+class TrainingClass(image, mask, index=0, class_prob=1.0)
+
+
+__init__(image, mask, index=0, class_prob=1.0)
+

Creates a new training class defined by applying mask to image.

+

Arguments:

+
+

image (spectral.Image or numpy.ndarray):

+
+

The MxNxB image over which the training class is defined.

+
+

mask (numpy.ndarray):

+
+

An MxN array of integers that specifies which pixels in +image are associated with the class.

+
+

index (int) [default 0]:

+
+

if index == 0, all nonzero elements of mask are associated +with the class. If index is nonzero, all elements of mask +equal to index are associated with the class.

+
+

class_prob (float) [default 1.0]:

+
+

Defines the prior probability associated with the class, which +is used in maximum likelihood classification. If classProb +is 1.0, prior probabilities are ignored by classifiers, giving +all class equal weighting.

+
+
+
+ +
+
+__iter__()
+

Returns an iterator over all samples for the class.

+
+ +
+
+calc_stats()
+

Calculates statistics for the class.

+

This function causes the stats attribute of the class to be +updated, where stats will have the following attributes:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +

Attribute

Type

Description

mean

numpy.ndarray

length-B mean vector

cov

numpy.ndarray

BxB covariance matrix

inv_cov

numpy.ndarray

Inverse of cov

log_det_cov

float

Natural log of determinant of cov

+
+ +
+
+size()
+

Returns the number of pixels/samples in the training set.

+
+ +
+
+stats_valid(tf=None)
+

Sets statistics for the TrainingClass to be valid or invalid.

+

Arguments:

+
+

tf (bool or None):

+
+

A value evaluating to False indicates that statistics should be +recalculated prior to being used. If the argument is None, +a value will be returned indicating whether stats need to be +recomputed.

+
+
+
+ +
+
+transform(transform)
+

Perform a linear transformation on the statistics of the training set.

+

Arguments:

+
+

transform (:class:numpy.ndarray or LinearTransform):

+
+

The linear transform array. If the class has B bands, then +transform must have shape (C,B).

+
+
+

After transform is applied, the class statistics will have C bands.

+
+ +
+ +
+
+

TraningClassSet

+
+
+class TrainingClassSet
+

A class to manage a set of TrainingClass objects.

+
+
+__getitem__(i)
+

Returns the training class having ID i.

+
+ +
+
+__iter__()
+

An iterator over all training classes in the set.

+
+ +
+
+__len__()
+

Returns number of training classes in the set.

+
+ +
+
+add_class(cl)
+

Adds a new class to the training set.

+

Arguments:

+
+

cl (spectral.TrainingClass):

+
+

cl.index must not duplicate a class already in the set.

+
+
+
+ +
+
+all_samples()
+

An iterator over all samples in all classes.

+
+ +
+
+transform(X)
+

Applies linear transform, M, to all training classes.

+

Arguments:

+
+

X (:class:numpy.ndarray):

+
+

The linear transform array. If the classes have B bands, then +X must have shape (C,B).

+
+
+

After the transform is applied, all classes will have C bands.

+
+ +
+ +
+
+
+

Spectral Classes/Functions

+
+

Adaptive Coherence/Cosine Estimator (ACE)

+
+
+ace(X, target, background=None, window=None, cov=None, **kwargs)
+

Returns Adaptive Coherence/Cosine Estimator (ACE) detection scores.

+

Usage:

+
+

y = ace(X, target, background)

+

y = ace(X, target, window=<win> [, cov=<cov>])

+
+

Arguments:

+
+

X (numpy.ndarray):

+
+

For the first calling method shown, X can be an ndarray with +shape (R, C, B) or an ndarray of shape (R * C, B). If the +background keyword is given, it will be used for the image +background statistics; otherwise, background statistics will be +computed from X.

+

If the window keyword is given, X must be a 3-dimensional +array and background statistics will be computed for each point +in the image using a local window defined by the keyword.

+
+

target (ndarray or sequence of ndarray):

+
+

If X has shape (R, C, B), target can be any of the following:

+
+

A length-B ndarray. In this case, target specifies a single +target spectrum to be detected. The return value will be an +ndarray with shape (R, C).

+

An ndarray with shape (D, B). In this case, target contains +D length-B targets that define a subspace for the detector. +The return value will be an ndarray with shape (R, C).

+

A length-D sequence (e.g., list or tuple) of length-B ndarrays. +In this case, the detector will be applied seperately to each of +the D targets. This is equivalent to calling the function +sequentially for each target and stacking the results but is +much faster. The return value will be an ndarray with shape +(R, C, D).

+
+
+

background (GaussianStats):

+
+

The Gaussian statistics for the background (e.g., the result +of calling calc_stats for an image). This argument is not +required if window is given.

+
+

window (2-tuple of odd integers):

+
+

Must have the form (inner, outer), where the two values +specify the widths (in pixels) of inner and outer windows centered +about the pixel being evaulated. Both values must be odd integers. +The background mean and covariance will be estimated from pixels +in the outer window, excluding pixels within the inner window. For +example, if (inner, outer) = (5, 21), then the number of +pixels used to estimate background statistics will be +21^2 - 5^2 = 416. If this argument is given, background +is not required (and will be ignored, if given).

+

The window is modified near image borders, where full, centered +windows cannot be created. The outer window will be shifted, as +needed, to ensure that the outer window still has height and width +outer (in this situation, the pixel being evaluated will not be +at the center of the outer window). The inner window will be +clipped, as needed, near image borders. For example, assume an +image with 145 rows and columns. If the window used is +(5, 21), then for the image pixel at (0, 0) (upper left corner), +the the inner window will cover image[:3, :3] and the outer +window will cover image[:21, :21]. For the pixel at (50, 1), the +inner window will cover image[48:53, :4] and the outer window +will cover image[40:51, :21].

+
+

cov (ndarray):

+
+

An optional covariance to use. If this parameter is given, cov +will be used for all matched filter calculations (background +covariance will not be recomputed in each window) and only the +background mean will be recomputed in each window. If the +window argument is specified, providing cov will allow the +result to be computed much faster.

+
+
+

Keyword Arguments:

+
+

vectorize (bool, default True):

+
+

Specifies whether the function should attempt to vectorize +operations. This typicall results in faster computation but will +consume more memory.

+
+
+

Returns numpy.ndarray:

+
+

The return value will be the ACE scores for each input pixel. The shape +of the returned array will be either (R, C) or (R, C, D), depending on +the value of the target argument.

+
+

References:

+

Kraut S. & Scharf L.L., “The CFAR Adaptive Subspace Detector is a Scale- +Invariant GLRT,” IEEE Trans. Signal Processing., vol. 47 no. 9, pp. 2538-41, +Sep. 1999

+
+ +
+
+

AsterDatabase

+
+
+class AsterDatabase(sqlite_filename=None)
+

A relational database to manage ASTER spectral library data.

+
+
+classmethod create(filename, aster_data_dir=None)
+

Creates an ASTER relational database by parsing ASTER data files.

+

Arguments:

+
+

filename (str):

+
+

Name of the new sqlite database file to create.

+
+

aster_data_dir (str):

+
+

Path to the directory containing ASTER library data files. If +this argument is not provided, no data will be imported.

+
+
+

Returns:

+
+

An AsterDatabase object.

+
+

Example:

+
>>> AsterDatabase.create("aster_lib.db", "/CDROM/ASTER2.0/data")
+
+
+

This is a class method (it does not require instantiating an +AsterDatabase object) that creates a new database by parsing all of the +files in the ASTER library data directory. Normally, this should only +need to be called once. Subsequently, a corresponding database object +can be created by instantiating a new AsterDatabase object with the +path the database file as its argument. For example:

+
>>> from spectral.database.aster import AsterDatabase
+>>> db = AsterDatabase("aster_lib.db")
+
+
+
+ +
+
+create_envi_spectral_library(spectrumIDs, bandInfo)
+

Creates an ENVI-formatted spectral library for a list of spectra.

+

Arguments:

+
+

spectrumIDs (list of ints):

+
+

List of SpectrumID values for of spectra in the “Spectra” +table of the ASTER database.

+
+

bandInfo (BandInfo):

+
+

The spectral bands to which the original ASTER library spectra +will be resampled.

+
+
+

Returns:

+
+

A SpectralLibrary object.

+
+

The IDs passed to the method should correspond to the SpectrumID field +of the ASTER database “Spectra” table. All specified spectra will be +resampled to the same discretization specified by the bandInfo +parameter. See spectral.BandResampler for details on the +resampling method used.

+
+ +
+
+get_signature(spectrumID)
+

Returns a spectrum with some additional metadata.

+

Usage:

+
sig = aster.get_signature(spectrumID)
+
+
+

Arguments:

+
+

spectrumID (int):

+
+

The SpectrumID value for the desired spectrum from the +Spectra table in the database.

+
+
+

Returns:

+
+

sig (Signature):

+
+

An object with the following attributes:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Attribute

Type

Description

measurement_id

int

SpectrumID value from Spectra table

sample_name

str

Sample from the Samples table

sample_id

int

SampleID from the Samples table

x

list

list of band center wavelengths

y

list

list of spectrum values for each band

+
+
+
+ +
+
+get_spectrum(spectrumID)
+

Returns a spectrum from the database.

+

Usage:

+
+

(x, y) = aster.get_spectrum(spectrumID)

+
+

Arguments:

+
+

spectrumID (int):

+
+

The SpectrumID value for the desired spectrum from the +Spectra table in the database.

+
+
+

Returns:

+
+

x (list):

+
+

Band centers for the spectrum.

+
+

y (list):

+
+

Spectrum data values for each band.

+
+
+

Returns a pair of vectors containing the wavelengths and measured +values values of a measurment. For additional metadata, call +“get_signature” instead.

+
+ +
+
+print_query(sql, args=None)
+

Prints the text result of an arbitrary SQL statement.

+

Arguments:

+
+

sql (str):

+
+

An SQL statement to be passed to the database. Use “?” for +variables passed into the statement.

+
+

args (tuple):

+
+

Optional arguments which will replace the “?” placeholders in +the sql argument.

+
+
+

This function performs the same query function as +spectral.database.Asterdatabase.query except query results are +printed to stdout instead of returning a cursor object.

+

Example:

+
>>> sql = r'SELECT SpectrumID, Name FROM Samples, Spectra ' +
+...        'WHERE Spectra.SampleID = Samples.SampleID ' +
+...        'AND Name LIKE "%grass%" AND MinWavelength < ?'
+>>> args = (0.5,)
+>>> db.print_query(sql, args)
+356|dry grass
+357|grass
+
+
+
+ +
+
+query(sql, args=None)
+

Returns the result of an arbitrary SQL statement.

+

Arguments:

+
+

sql (str):

+
+

An SQL statement to be passed to the database. Use “?” for +variables passed into the statement.

+
+

args (tuple):

+
+

Optional arguments which will replace the “?” placeholders in +the sql argument.

+
+
+

Returns:

+
+

An sqlite3.Cursor object with the query results.

+
+

Example:

+
>>> sql = r'SELECT SpectrumID, Name FROM Samples, Spectra ' +
+...        'WHERE Spectra.SampleID = Samples.SampleID ' +
+...        'AND Name LIKE "%grass%" AND MinWavelength < ?'
+>>> args = (0.5,)
+>>> cur = db.query(sql, args)
+>>> for row in cur:
+...     print row
+...
+(356, u'dry grass')
+(357, u'grass')
+
+
+
+ +
+ +
+
+

BandInfo

+
+
+class BandInfo
+

A BandInfo object characterizes the spectral bands associated with an +image. All BandInfo member variables are optional. For N bands, all +members of type <list> will have length N and contain float values.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Member

Description

Default

centers

List of band centers

None

bandwidths

List of band FWHM values

None

centers_stdevs

List of std devs of band centers

None

bandwidth_stdevs

List of std devs of bands FWHMs

None

band_quantity

Image data type (e.g., “reflectance”)

“”

band_unit

Band unit (e.g., “nanometer”)

“”

+
+ +
+
+

BandResampler

+
+
+class BandResampler(centers1, centers2, fwhm1=None, fwhm2=None)
+

A callable object for resampling spectra between band discretizations.

+

A source band will contribute to any destination band where there is +overlap between the FWHM of the two bands. If there is an overlap, an +integral is performed over the region of overlap assuming the source band +data value is constant over its FWHM (since we do not know the true +spectral load over the source band) and the destination band has a Gaussian +response function. Any target bands that do not have any overlapping source +bands will contain NaN as the resampled band value.

+

If bandwidths are not specified for source or destination bands, the bands +are assumed to have FWHM values that span half the distance to the adjacent +bands.

+
+
+__call__(spectrum)
+

Takes a source spectrum as input and returns a resampled spectrum.

+

Arguments:

+
+

spectrum (list or numpy.ndarray):

+
+

list or vector of values to be resampled. Must have same +length as the source band discretiation used to created the +resampler.

+
+
+

Returns:

+
+

A resampled rank-1 numpy.ndarray with length corresponding +to the destination band discretization used to create the resampler.

+
+

Any target bands that do not have at lease one overlapping source band +will contain float(‘nan’) as the resampled band value.

+
+ +
+
+__init__(centers1, centers2, fwhm1=None, fwhm2=None)
+

BandResampler constructor.

+

Usage:

+
+

resampler = BandResampler(bandInfo1, bandInfo2)

+

resampler = BandResampler(centers1, centers2, [fwhm1 = None [, fwhm2 = None]])

+
+

Arguments:

+
+

bandInfo1 (BandInfo):

+
+

Discretization of the source bands.

+
+

bandInfo2 (BandInfo):

+
+

Discretization of the destination bands.

+
+

centers1 (list):

+
+

floats defining center values of source bands.

+
+

centers2 (list):

+
+

floats defining center values of destination bands.

+
+

fwhm1 (list):

+
+

Optional list defining FWHM values of source bands.

+
+

fwhm2 (list):

+
+

Optional list defining FWHM values of destination bands.

+
+
+

Returns:

+
+

A callable BandResampler object that takes a spectrum corresponding +to the source bands and returns the spectrum resampled to the +destination bands.

+
+

If bandwidths are not specified, the associated bands are assumed to +have FWHM values that span half the distance to the adjacent bands.

+
+ +
+ +
+
+

Bhattacharyya Distance

+
+
+bdist(class1, class2)
+

Calulates the Bhattacharyya distance between two classes.

+

USAGE: bd = bdist(class1, class2)

+

Arguments:

+
+

class1, class2 (TrainingClass)

+
+

Returns:

+
+

A float value for the Bhattacharyya Distance between the classes. This +function is aliased to bDistance.

+
+

References:

+
+

Richards, J.A. & Jia, X. Remote Sensing Digital Image Analysis: An +Introduction. (Springer: Berlin, 1999).

+
+
+ +
+

Note

+

Since it is unlikely anyone can actually remember how to spell +“Bhattacharyya”, this function has been aliased to “bdist” +for convenience.

+
+
+
+

calc_stats

+
+
+calc_stats(image, mask=None, index=None, allow_nan=False)
+

Computes Gaussian stats for image data..

+

Arguments:

+
+

image (ndarrray, Image, or spectral.Iterator):

+
+

If an ndarray, it should have shape MxNxB and the mean & +covariance will be calculated for each band (third dimension).

+
+

mask (ndarray):

+
+

If mask is specified, mean & covariance will be calculated for +all pixels indicated in the mask array. If index is specified, +all pixels in image for which mask == index will be used; +otherwise, all nonzero elements of mask will be used.

+
+

index (int):

+
+

Specifies which value in mask to use to select pixels from +image. If not specified but mask is, then all nonzero elements +of mask will be used.

+
+

allow_nan (bool, default False):

+
+

If True, statistics will be computed even if np.nan values are +present in the data; otherwise, ~spectral.algorithms.spymath.NaNValueError +is raised.

+
+

If neither mask nor index are specified, all samples in vectors +will be used.

+
+

Returns:

+
+

GaussianStats object:

+
+

This object will have members mean, cov, and nsamples.

+
+
+
+ +
+
+

covariance

+
+
+covariance(*args)
+

Returns the covariance of the set of vectors.

+

Usage:

+
C = covariance(vectors [, mask=None [, index=None]])
+
+
+

Arguments:

+
+

vectors (ndarrray, Image, or spectral.Iterator):

+
+

If an ndarray, it should have shape MxNxB and the mean & +covariance will be calculated for each band (third dimension).

+
+

mask (ndarray):

+
+

If mask is specified, mean & covariance will be calculated for +all pixels indicated in the mask array. If index is specified, +all pixels in image for which mask == index will be used; +otherwise, all nonzero elements of mask will be used.

+
+

index (int):

+
+

Specifies which value in mask to use to select pixels from +image. If not specified but mask is, then all nonzero elements +of mask will be used.

+
+

If neither mask nor index are specified, all samples in vectors +will be used.

+
+

Returns:

+
+

C (ndarray):

+
+

The BxB unbiased estimate (dividing by N-1) of the covariance +of the vectors.

+
+
+

To also return the mean vector and number of samples, call +mean_cov instead.

+
+ +
+
+

cov_avg

+
+
+cov_avg(image, mask, weighted=True)
+

Calculates the covariance averaged over a set of classes.

+

Arguments:

+
+

image (ndarrray, Image, or spectral.Iterator):

+
+

If an ndarray, it should have shape MxNxB and the mean & +covariance will be calculated for each band (third dimension).

+
+

mask (integer-valued ndarray):

+
+

Elements specify the classes associated with pixels in image. +All pixels associeted with non-zero elements of mask will be +used in the covariance calculation.

+
+

weighted (bool, default True):

+
+

Specifies whether the individual class covariances should be +weighted when computing the average. If True, each class will +be weighted by the number of pixels provided for the class; +otherwise, a simple average of the class covariances is performed.

+
+
+

Returns a class-averaged covariance matrix. The number of covariances used +in the average is equal to the number of non-zero elements of mask.

+
+ +
+
+

EcostressDatabase

+
+
+class EcostressDatabase(sqlite_filename=None)
+

A relational database to manage ECOSTRESS spectral library data.

+
+
+classmethod create(filename, data_dir=None)
+

Creates an ECOSTRESS relational database by parsing ECOSTRESS data files.

+

Arguments:

+
+

filename (str):

+
+

Name of the new sqlite database file to create.

+
+

data_dir (str):

+
+

Path to the directory containing ECOSTRESS library data files. If +this argument is not provided, no data will be imported.

+
+
+

Returns:

+
+

An EcostressDatabase object.

+
+

Example:

+
>>> EcostressDatabase.create("ecostress.db", "./eco_data_ver1/")
+
+
+

This is a class method (it does not require instantiating an +EcostressDatabase object) that creates a new database by parsing all of the +files in the ECOSTRESS library data directory. Normally, this should only +need to be called once. Subsequently, a corresponding database object +can be created by instantiating a new EcostressDatabase object with the +path the database file as its argument. For example:

+
>>> from spectral.database.ecostress import EcostressDatabase
+>>> db = EcostressDatabase("~/ecostress.db")
+
+
+
+ +
+ +
+
+

FisherLinearDiscriminant

+
+
+class FisherLinearDiscriminant(vals, vecs, mean, cov_b, cov_w)
+

An object for storing a data set’s linear discriminant data. For C +classes with B-dimensional data, the object has the following members:

+
+

eigenvalues:

+
+

A length C-1 array of eigenvalues

+
+

eigenvectors:

+
+

A BxC array of normalized eigenvectors

+
+

mean:

+
+

The length B mean vector of the image pixels (from all classes)

+
+

cov_b:

+
+

The BxB matrix of covariance between classes

+
+

cov_w:

+
+

The BxB matrix of average covariance within each class

+
+

transform:

+
+

A callable function to transform data to the space of the +linear discriminant.

+
+
+
+ +
+
+

GaussianClassifier

+
+
+class GaussianClassifier(training_data=None, min_samples=None)
+

A Gaussian Maximum Likelihood Classifier

+
+
+__init__(training_data=None, min_samples=None)
+

Creates the classifier and optionally trains it with training data.

+

Arguments:

+
+

training_data (TrainingClassSet):

+
+

The training classes on which to train the classifier.

+
+

min_samples (int) [default None]:

+
+

Minimum number of samples required from a training class to +include it in the classifier.

+
+
+
+ +
+
+classify_image(image)
+

Classifies an entire image, returning a classification map.

+

Arguments:

+
+

image (ndarray or spectral.Image)

+
+

The MxNxB image to classify.

+
+
+

Returns (ndarray):

+
+

An MxN ndarray of integers specifying the class for each pixel.

+
+
+ +
+
+classify_spectrum(x)
+

Classifies a pixel into one of the trained classes.

+

Arguments:

+
+

x (list or rank-1 ndarray):

+
+

The unclassified spectrum.

+
+
+

Returns:

+
+

classIndex (int):

+
+

The index for the TrainingClass +to which x is classified.

+
+
+
+ +
+
+train(training_data)
+

Trains the classifier on the given training data.

+

Arguments:

+
+

training_data (TrainingClassSet):

+
+

Data for the training classes.

+
+
+
+ +
+ +
+
+

GaussianStats

+
+
+class GaussianStats(mean=None, cov=None, nsamples=None, inv_cov=None)
+

A class for storing Gaussian statistics for a data set.

+

Statistics stored include:

+
+

mean:

+
+

Mean vector

+
+

cov:

+
+

Covariance matrix

+
+

nsamples:

+
+

Number of samples used in computing the statistics

+
+
+

Several derived statistics are computed on-demand (and cached) and are +available as property attributes. These include:

+
+

inv_cov:

+
+

Inverse of the covariance

+
+

sqrt_cov:

+
+

Matrix square root of covariance: sqrt_cov.dot(sqrt_cov) == cov

+
+

sqrt_inv_cov:

+
+

Matrix square root of the inverse of covariance

+
+

log_det_cov:

+
+

The log of the determinant of the covariance matrix

+
+

principal_components:

+
+

The principal components of the data, based on mean and cov.

+
+
+
+ +
+
+

kmeans

+
+
+kmeans(image, nclusters=10, max_iterations=20, **kwargs)
+

Performs iterative clustering using the k-means algorithm.

+

Arguments:

+
+

image (numpy.ndarray or spectral.Image):

+
+

The MxNxB image on which to perform clustering.

+
+

nclusters (int) [default 10]:

+
+

Number of clusters to create. The number produced may be less than +nclusters.

+
+

max_iterations (int) [default 20]:

+
+

Max number of iterations to perform.

+
+
+

Keyword Arguments:

+
+

start_clusters (numpy.ndarray) [default None]:

+
+

nclusters x B array of initial cluster centers. If not provided, +initial cluster centers will be spaced evenly along the diagonal of +the N-dimensional bounding box of the image data.

+
+

compare (callable object) [default None]:

+
+

Optional comparison function. compare must be a callable object +that takes 2 MxN numpy.ndarray objects as its arguments +and returns non-zero when clustering is to be terminated. The two +arguments are the cluster maps for the previous and current cluster +cycle, respectively.

+
+

distance (callable object) [default L2]:

+
+

The distance measure to use for comparison. The default is to use +L2 (Euclidean) distance. For Manhattan distance, specify +L1.

+
+

frames (list) [default None]:

+
+

If this argument is given and is a list object, each intermediate +cluster map is appended to the list.

+
+
+

Returns a 2-tuple containing:

+
+

class_map (numpy.ndarray):

+
+

An MxN array whos values are the indices of the cluster for the +corresponding element of image.

+
+

centers (numpy.ndarray):

+
+

An nclusters x B array of cluster centers.

+
+
+

Iterations are performed until clusters converge (no pixels reassigned +between iterations), maxIterations is reached, or compare returns +nonzero. If KeyboardInterrupt is generated (i.e., CTRL-C pressed) +while the algorithm is executing, clusters are returned from the previously +completed iteration.

+
+ +
+
+

linear_discriminant

+
+
+linear_discriminant(classes, whiten=True)
+

Solve Fisher’s linear discriminant for eigenvalues and eigenvectors.

+

Usage: (L, V, Cb, Cw) = linear_discriminant(classes)

+

Arguments:

+
+

classes (TrainingClassSet):

+
+

The set of C classes to discriminate.

+
+
+

Returns a FisherLinearDiscriminant object containing the within/between- +class covariances, mean vector, and a callable transform to convert data to +the transform’s space.

+

This function determines the solution to the generalized eigenvalue problem

+
+

Cb * x = lambda * Cw * x

+
+

Since cov_w is normally invertable, the reduces to

+
+

(inv(Cw) * Cb) * x = lambda * x

+
+

References:

+
+

Richards, J.A. & Jia, X. Remote Sensing Digital Image Analysis: An +Introduction. (Springer: Berlin, 1999).

+
+
+ +
+
+

LinearTransform

+
+
+class LinearTransform(A, **kwargs)
+

A callable linear transform object.

+

In addition to the __call__ method, which applies the transform to given, +data, a LinearTransform object also has the following members:

+
+

dim_in (int):

+
+

The expected length of input vectors. This will be None if the +input dimension is unknown (e.g., if the transform is a scalar).

+
+

dim_out (int):

+
+

The length of output vectors (after linear transformation). This +will be None if the input dimension is unknown (e.g., if +the transform is a scalar).

+
+

dtype (numpy dtype):

+
+

The numpy dtype for the output ndarray data.

+
+
+
+
+__call__(X)
+

Applies the linear transformation to the given data.

+

Arguments:

+
+

X (ndarray or object with transform method):

+
+

If X is an ndarray, it is either an (M,N,K) array containing +M*N length-K vectors to be transformed or it is an (R,K) array +of length-K vectors to be transformed. If X is an object with +a method named transform the result of passing the +LinearTransform object to the transform method will be +returned.

+
+
+

Returns an (M,N,J) or (R,J) array, depending on shape of X, where J +is the length of the first dimension of the array A passed to +__init__.

+
+ +
+
+__init__(A, **kwargs)
+

Arguments:

+
+

A (ndarrray):

+
+

An (J,K) array to be applied to length-K targets.

+
+
+

Keyword Argments:

+
+

pre (scalar or length-K sequence):

+
+

Additive offset to be applied prior to linear transformation.

+
+

post (scalar or length-J sequence):

+
+

An additive offset to be applied after linear transformation.

+
+

dtype (numpy dtype):

+
+

Explicit type for transformed data.

+
+
+
+ +
+
+chain(transform)
+

Chains together two linear transforms. +If the transform f1 is given by

+
+

F_1(X) = A_1(X + b_1) + c_1

+

and f2 by

+
+

F_2(X) = A_2(X + b_2) + c_2

+

then f1.chain(f2) returns a new LinearTransform, f3, whose output +is given by

+
+

F_3(X) = F_2(F_1(X))

+
+ +
+ +
+
+

MahalanobisDistanceClassifier

+
+
+class MahalanobisDistanceClassifier(training_data=None, min_samples=None)
+

A Classifier using Mahalanobis distance for class discrimination

+
+
+__init__(training_data=None, min_samples=None)
+

Creates the classifier and optionally trains it with training data.

+

Arguments:

+
+

training_data (TrainingClassSet):

+
+

The training classes on which to train the classifier.

+
+

min_samples (int) [default None]:

+
+

Minimum number of samples required from a training class to +include it in the classifier.

+
+
+
+ +
+
+classify_image(image)
+

Classifies an entire image, returning a classification map.

+

Arguments:

+
+

image (ndarray or spectral.Image)

+
+

The MxNxB image to classify.

+
+
+

Returns (ndarray):

+
+

An MxN ndarray of integers specifying the class for each pixel.

+
+
+ +
+
+classify_spectrum(x)
+

Classifies a pixel into one of the trained classes.

+

Arguments:

+
+

x (list or rank-1 ndarray):

+
+

The unclassified spectrum.

+
+
+

Returns:

+
+

classIndex (int):

+
+

The index for the TrainingClass +to which x is classified.

+
+
+
+ +
+
+train(trainingData)
+

Trains the classifier on the given training data.

+

Arguments:

+
+

trainingData (TrainingClassSet):

+
+

Data for the training classes.

+
+
+
+ +
+ +
+
+

map_class_ids

+
+
+map_class_ids(src_class_image, dest_class_image, unlabeled=None)
+

Create a mapping between class labels in two classification images.

+

Running a classification algorithm (particularly an unsupervised one) +multiple times on the same image can yield similar results but with +different class labels (indices) for the same classes. This function +produces a mapping of class indices from one classification image to +another by finding class indices that share the most pixels between the +two classification images.

+

Arguments:

+
+

src_class_image (ndarray):

+
+

An MxN integer array of class indices. The indices in this array +will be mapped to indices in dest_class_image.

+
+

dest_class_image (ndarray):

+
+

An MxN integer array of class indices.

+
+

unlabeled (int or array of ints):

+
+

If this argument is provided, all pixels (in both images) will be +ignored when counting coincident pixels to determine the mapping. +If mapping a classification image to a ground truth image that has +a labeled background value, set unlabeled to that value.

+
+
+

Return Value:

+
+

A dictionary whose keys are class indices from src_class_image and +whose values are class indices from dest_class_image.

+
+
+

See also

+

map_classes

+
+
+ +
+
+

map_classes

+
+
+map_classes(class_image, class_id_map, allow_unmapped=False)
+

Modifies class indices according to a class index mapping.

+

Arguments:

+
+

class_image: (ndarray):

+
+

An MxN array of integer class indices.

+
+

class_id_map: (dict):

+
+

A dict whose keys are indices from class_image and whose values +are new values for the corresponding indices. This value is +usually the output of map_class_ids.

+
+

allow_unmapped (bool, default False):

+
+

A flag indicating whether class indices can appear in class_image +without a corresponding key in class_id_map. If this value is +False and an index in the image is found without a mapping key, +a ValueError is raised. If True, the unmapped index will +appear unmodified in the output image.

+
+
+

Return Value:

+
+

An integer-valued ndarray with same shape as class_image

+
+

Example:

+
>>> m = spy.map_class_ids(result, gt, unlabeled=0)
+>>> result_mapped = spy.map_classes(result, m)
+
+
+
+

See also

+

map_class_ids

+
+
+ +
+
+

matched_filter

+
+
+matched_filter(X, target, background=None, window=None, cov=None)
+

Computes a linear matched filter target detector score.

+

Usage:

+
+

y = matched_filter(X, target, background)

+

y = matched_filter(X, target, window=<win> [, cov=<cov>])

+
+

Given target/background means and a common covariance matrix, the matched +filter response is given by:

+
+

y=\frac{(\mu_t-\mu_b)^T\Sigma^{-1}(x-\mu_b)}{(\mu_t-\mu_b)^T\Sigma^{-1}(\mu_t-\mu_b)}

+

where \mu_t is the target mean, \mu_b is the background +mean, and \Sigma is the covariance.

+

Arguments:

+
+

X (numpy.ndarray):

+
+

For the first calling method shown, X can be an image with +shape (R, C, B) or an ndarray of shape (R * C, B). If the +background keyword is given, it will be used for the image +background statistics; otherwise, background statistics will be +computed from X.

+

If the window keyword is given, X must be a 3-dimensional +array and background statistics will be computed for each point +in the image using a local window defined by the keyword.

+
+

target (ndarray):

+
+

Length-K vector specifying the target to be detected.

+
+

background (GaussianStats):

+
+

The Gaussian statistics for the background (e.g., the result +of calling calc_stats for an image). This argument is not +required if window is given.

+
+

window (2-tuple of odd integers):

+
+

Must have the form (inner, outer), where the two values +specify the widths (in pixels) of inner and outer windows centered +about the pixel being evaulated. Both values must be odd integers. +The background mean and covariance will be estimated from pixels +in the outer window, excluding pixels within the inner window. For +example, if (inner, outer) = (5, 21), then the number of +pixels used to estimate background statistics will be +21^2 - 5^2 = 416. If this argument is given, background +is not required (and will be ignored, if given).

+

The window is modified near image borders, where full, centered +windows cannot be created. The outer window will be shifted, as +needed, to ensure that the outer window still has height and width +outer (in this situation, the pixel being evaluated will not be +at the center of the outer window). The inner window will be +clipped, as needed, near image borders. For example, assume an +image with 145 rows and columns. If the window used is +(5, 21), then for the image pixel at (0, 0) (upper left corner), +the the inner window will cover image[:3, :3] and the outer +window will cover image[:21, :21]. For the pixel at (50, 1), the +inner window will cover image[48:53, :4] and the outer window +will cover image[40:51, :21].

+
+

cov (ndarray):

+
+

An optional covariance to use. If this parameter is given, cov +will be used for all matched filter calculations (background +covariance will not be recomputed in each window) and only the +background mean will be recomputed in each window. If the +window argument is specified, providing cov will allow the +result to be computed much faster.

+
+
+

Returns numpy.ndarray:

+
+

The return value will be the matched filter scores distance) for each +pixel given. If X has shape (R, C, K), the returned ndarray will +have shape (R, C).

+
+
+ +
+
+

MatchedFilter

+
+
+class MatchedFilter(background, target)
+

A callable linear matched filter.

+

Given target/background means and a common covariance matrix, the matched +filter response is given by:

+
+

y=\frac{(\mu_t-\mu_b)^T\Sigma^{-1}(x-\mu_b)}{(\mu_t-\mu_b)^T\Sigma^{-1}(\mu_t-\mu_b)}

+

where \mu_t is the target mean, \mu_b is the background +mean, and \Sigma is the covariance.

+
+
+__call__(X)
+

Applies the linear transformation to the given data.

+

Arguments:

+
+

X (ndarray or object with transform method):

+
+

If X is an ndarray, it is either an (M,N,K) array containing +M*N length-K vectors to be transformed or it is an (R,K) array +of length-K vectors to be transformed. If X is an object with +a method named transform the result of passing the +LinearTransform object to the transform method will be +returned.

+
+
+

Returns an (M,N,J) or (R,J) array, depending on shape of X, where J +is the length of the first dimension of the array A passed to +__init__.

+
+ +
+
+__init__(background, target)
+

Creates the filter, given background/target means and covariance.

+

Arguments:

+
+

background (GaussianStats):

+
+

The Gaussian statistics for the background (e.g., the result +of calling calc_stats).

+
+

target (ndarray):

+
+

Length-K target mean

+
+
+
+ +
+
+whiten(X)
+

Transforms data to the whitened space of the background.

+

Arguments:

+
+

X (ndarray):

+
+

Size (M,N,K) or (M*N,K) array of length K vectors to transform.

+
+
+

Returns an array of same size as X but linearly transformed to the +whitened space of the filter.

+
+ +
+ +
+
+

mean_cov

+
+
+mean_cov(image, mask=None, index=None)
+

Return the mean and covariance of the set of vectors.

+

Usage:

+
(mean, cov, S) = mean_cov(vectors [, mask=None [, index=None]])
+
+
+

Arguments:

+
+

image (ndarrray, Image, or spectral.Iterator):

+
+

If an ndarray, it should have shape MxNxB and the mean & +covariance will be calculated for each band (third dimension).

+
+

mask (ndarray):

+
+

If mask is specified, mean & covariance will be calculated for +all pixels indicated in the mask array. If index is specified, +all pixels in image for which mask == index will be used; +otherwise, all nonzero elements of mask will be used.

+
+

index (int):

+
+

Specifies which value in mask to use to select pixels from +image. If not specified but mask is, then all nonzero elements +of mask will be used.

+
+

If neither mask nor index are specified, all samples in vectors +will be used.

+
+

Returns a 3-tuple containing:

+
+

mean (ndarray):

+
+

The length-B mean vectors

+
+

cov (ndarray):

+
+

The BxB unbiased estimate (dividing by N-1) of the covariance +of the vectors.

+
+

S (int):

+
+

Number of samples used to calculate mean & cov

+
+
+

Calculate the mean and covariance of of the given vectors. The argument +can be an Iterator, a SpyFile object, or an MxNxB array.

+
+ +
+
+

Minimum Noise Fraction (MNF)

+
+
+mnf(signal, noise)
+

Computes Minimum Noise Fraction / Noise-Adjusted Principal Components.

+

Arguments:

+
+

signal (GaussianStats):

+
+

Estimated signal statistics

+
+

noise (GaussianStats):

+
+

Estimated noise statistics

+
+
+

Returns an MNFResult object, +containing the Noise-Adjusted Principal Components (NAPC) and methods for +denoising or reducing dimensionality of associated data.

+

The Minimum Noise Fraction (MNF) is similar to the Principal Components +transformation with the difference that the Principal Components associated +with the MNF are ordered by descending signal-to-noise ratio (SNR) rather +than overall image variance. Note that the eigenvalues of the NAPC are +equal to one plus the SNR in the transformed space (since noise has +whitened unit variance in the NAPC coordinate space).

+

Example:

+
>>> data = open_image('92AV3C.lan').load()
+>>> signal = calc_stats(data)
+>>> noise = noise_from_diffs(data[117: 137, 85: 122, :])
+>>> mnfr = mnf(signal, noise)
+
+
+
>>> # De-noise the data by eliminating NAPC components where SNR < 10.
+>>> # The de-noised data will be in the original coordinate space (at
+>>> # full dimensionality).
+>>> denoised = mnfr.denoise(data, snr=10)
+
+
+
>>> # Reduce dimensionality, retaining NAPC components where SNR >= 10.
+>>> reduced = mnfr.reduce(data, snr=10)
+
+
+
>>> # Reduce dimensionality, retaining top 50 NAPC components.
+>>> reduced = mnfr.reduce(data, num=50)
+
+
+

References:

+
+

Lee, James B., A. Stephen Woodyatt, and Mark Berman. “Enhancement of +high spectral resolution remote-sensing data by a noise-adjusted +principal components transform.” Geoscience and Remote Sensing, IEEE +Transactions on 28.3 (1990): 295-304.

+
+
+ +
+
+class MNFResult(signal, noise, napc)
+

Result object returned by mnf.

+

This object contains data associates with a Minimum Noise Fraction +calculation, including signal and noise statistics, as well as the +Noise-Adjusted Principal Components (NAPC). This object can be used to +denoise image data or to reduce its dimensionality.

+
+
+denoise(X, **kwargs)
+

Returns a de-noised version of X.

+

Arguments:

+
+

X (np.ndarray):

+
+

Data to be de-noised. Can be a single pixel or an image.

+
+
+

One (and only one) of the following keywords must be specified:

+
+

num (int):

+
+

Number of Noise-Adjusted Principal Components to retain.

+
+

snr (float):

+
+

Threshold signal-to-noise ratio (SNR) to retain.

+
+
+

Returns denoised image data with same shape as X.

+

Note that calling this method is equivalent to calling the +get_denoising_transform method with same keyword and applying the +returned transform to X. If you only intend to denoise data with the +same parameters multiple times, then it is more efficient to get the +denoising transform and reuse it, rather than calling this method +multilple times.

+
+ +
+
+get_denoising_transform(**kwargs)
+

Returns a function for denoising image data.

+

One (and only one) of the following keywords must be specified:

+
+

num (int):

+
+

Number of Noise-Adjusted Principal Components to retain.

+
+

snr (float):

+
+

Threshold signal-to-noise ratio (SNR) to retain.

+
+
+

Returns a callable LinearTransform +object for denoising image data.

+
+ +
+
+get_reduction_transform(**kwargs)
+

Reduces dimensionality of image data.

+

One (and only one) of the following keywords must be specified:

+
+

num (int):

+
+

Number of Noise-Adjusted Principal Components to retain.

+
+

snr (float):

+
+

Threshold signal-to-noise ratio (SNR) to retain.

+
+
+

Returns a callable LinearTransform +object for reducing the dimensionality of image data.

+
+ +
+
+num_with_snr(snr)
+

Returns the number of components with SNR >= snr.

+
+ +
+
+reduce(X, **kwargs)
+

Reduces dimensionality of image data.

+

Arguments:

+
+

X (np.ndarray):

+
+

Data to be reduced. Can be a single pixel or an image.

+
+
+

One (and only one) of the following keywords must be specified:

+
+

num (int):

+
+

Number of Noise-Adjusted Principal Components to retain.

+
+

snr (float):

+
+

Threshold signal-to-noise ratio (SNR) to retain.

+
+
+

Returns a verions of X with reduced dimensionality.

+

Note that calling this method is equivalent to calling the +get_reduction_transform method with same keyword and applying the +returned transform to X. If you intend to denoise data with the +same parameters multiple times, then it is more efficient to get the +reduction transform and reuse it, rather than calling this method +multilple times.

+
+ +
+ +
+
+

msam

+
+
+msam(data, members)
+

Modified SAM scores according to Oshigami, et al [1]. Endmembers are +mean-subtracted prior to spectral angle calculation. Results are +normalized such that the maximum value of 1 corresponds to a perfect match +(zero spectral angle).

+

Arguments:

+
+

data (numpy.ndarray or spectral.Image):

+
+

An MxNxB image for which spectral angles will be calculated.

+
+

members (numpy.ndarray):

+
+

CxB array of spectral endmembers.

+
+
+

Returns:

+
+

MxNxC array of MSAM scores with maximum value of 1 corresponding +to a perfect match (zero spectral angle).

+
+

Calculates the spectral angles between each vector in data and each of the +endmembers. The output of this function (angles) can be used to classify +the data by minimum spectral angle by calling argmax(angles).

+

References:

+

[1] Shoko Oshigami, Yasushi Yamaguchi, Tatsumi Uezato, Atsushi Momose, +Yessy Arvelyna, Yuu Kawakami, Taro Yajima, Shuichi Miyatake, and +Anna Nguno. 2013. Mineralogical mapping of southern Namibia by application +of continuum-removal MSAM method to the HyMap data. Int. J. Remote Sens. +34, 15 (August 2013), 5282-5295.

+
+ +
+
+

ndvi

+
+
+ndvi(data, red, nir)
+

Calculates Normalized Difference Vegetation Index (NDVI).

+

Arguments:

+
+

data (ndarray or spectral.Image):

+
+

The array or SpyFile for which to calculate the index.

+
+

red (int or int range):

+
+

Index of the red band or an index range for multiple bands.

+
+

nir (int or int range):

+
+

An integer index of the near infrared band or an index range for +multiple bands.

+
+
+

Returns an ndarray:

+
+

An array containing NDVI values in the range [-1.0, 1.0] for each +corresponding element of data.

+
+
+ +
+
+

noise_from_diffs

+
+
+noise_from_diffs(X, direction='lowerright')
+

Estimates noise statistcs by taking differences of adjacent pixels.

+

Arguments:

+
+

X (np.ndarray):

+
+

The data from which to estimage noise statistics. X should have +shape (nrows, ncols, nbands).

+
+

direction (str, default “lowerright”):

+
+

The pixel direction along which to calculate pixel differences. +Must be one of the following:

+
+
+
‘lowerright’:

Take difference with pixel diagonally to lower right

+
+
‘lowerleft’:

Take difference with pixel diagonally to lower right

+
+
‘right’:

Take difference with pixel to the right

+
+
‘lower’:

Take differenece with pixel below

+
+
+
+
+
+

Returns a GaussianStats object.

+
+ +
+
+

orthogonalize

+
+
+orthogonalize(vecs, start=0)
+

Performs Gram-Schmidt Orthogonalization on a set of vectors.

+

Arguments:

+
+

vecs (numpy.ndarray):

+
+

The set of vectors for which an orthonormal basis will be created. +If there are C vectors of length B, vecs should be CxB.

+
+

start (int) [default 0]:

+
+

If start > 0, then vecs[start] will be assumed to already be +orthonormal.

+
+
+

Returns:

+
+

A new CxB containing an orthonormal basis for the given vectors.

+
+
+ +
+
+

PerceptronClassifier

+
+
+class PerceptronClassifier(layers, k=1.0)
+

A multi-layer perceptron classifier with backpropagation learning.

+

Multi-layer perceptrons often require many (i.e., thousands) of iterations +through the traning data to converge on a solution. Therefore, it is not +recommended to attempt training a network on full-dimensional hyperspectral +data or even on a full set of image pixels. It is likely preferable to +first train the network on a subset of the data, then retrain the network +(starting with network weights from initial training) on the full data +set.

+

Example usage: Train an MLP with 20 samples from each training class after +performing dimensionality reduction:

+
>>> classes = create_training_classes(data, gt)
+>>> fld = linear_discriminant(classes)
+>>> xdata = fld.transform(data)
+>>> classes = create_training_classes(xdata, gt)
+>>> nfeatures = xdata.shape[-1]
+>>> nclasses = len(classes)
+>>> 
+>>> p = PerceptronClassifier([nfeatures, 20, 8, nclasses])
+>>> p.train(classes, 20, clip=0., accuracy=100., batch=1,
+>>>         momentum=0.3, rate=0.3)
+>>> c = p.classify(xdata)
+
+
+
+
+classify(X, **kwargs)
+

Classifies the given sample. +This has the same result as calling input and rounding the result.

+
+ +
+
+classify_image(image)
+

Classifies an entire image, returning a classification map.

+

Arguments:

+
+

image (ndarray or spectral.Image)

+
+

The MxNxB image to classify.

+
+
+

Returns (ndarray):

+
+

An MxN ndarray of integers specifying the class for each pixel.

+
+
+ +
+
+classify_spectrum(x)
+

Classifies a pixel into one of the trained classes.

+

Arguments:

+
+

x (list or rank-1 ndarray):

+
+

The unclassified spectrum.

+
+
+

Returns:

+
+

classIndex (int):

+
+

The index for the TrainingClass +to which x is classified.

+
+
+
+ +
+
+input(x, clip=0.0)
+

Sets Perceptron input, activates neurons and sets & returns output.

+

Arguments:

+
+

x (sequence):

+
+

Inputs to input layer. Should not include a bias input.

+
+

clip (float >= 0):

+
+

Optional clipping value to limit sigmoid output. The sigmoid +function has output in the range (0, 1). If the clip argument +is set to a then all neuron outputs for the layer will be +constrained to the range [a, 1 - a]. This can improve perceptron +learning rate in some situations.

+
+
+

For classifying samples, call classify instead of input.

+
+ +
+
+train(training_data, samples_per_class=0, *args, **kwargs)
+

Trains the Perceptron on the training data.

+

Arguments:

+
+

training_data (TrainingClassSet):

+
+

Data for the training classes.

+
+

samples_per_class (int):

+
+

Maximum number of training observations to user from each +class in training_data. If this argument is not provided, +all training data is used.

+
+
+

Keyword Arguments:

+
+

accuracy (float):

+
+

The percent training accuracy at which to terminate training, if +the maximum number of iterations are not reached first. This +value can be set greater than 100 to force a specified number of +training iterations to be performed (e.g., to continue reducing +the error term after 100% classification accuracy has been +achieved.

+
+

rate (float):

+
+

The perceptron learning rate (typically in the range (0, 1]).

+
+

momentum (float):

+
+

The perceptron learning momentum term, which specifies the +fraction of the previous update value that should be added to +the current update term. The value should be in the range [0, 1).

+
+

batch (positive integer):

+
+

Specifies how many samples should be evaluated before an update +is made to the perceptron weights. A value of 0 indicates batch +updates should be performed (evaluate all training inputs prior +to updating). Otherwise, updates will be aggregated for every +batch inputs (i.e., batch == 1 is stochastic learning).

+
+

clip (float >= 0):

+
+

Optional clipping value to limit sigmoid output during training. +The sigmoid function has output in the range (0, 1). If the +clip argument is set to a then all neuron outputs for the +layer will be constrained to the range [a, 1 - a]. This can +improve perceptron learning rate in some situations.

+

After training the perceptron with a clipping value, train can +be called again with clipping set to 0 to continue reducing the +training error.

+
+

on_iteration (callable):

+
+

A callable object that accepts the perceptron as input and +returns bool. If this argument is set, the object will be called +at the end of each training iteration with the perceptron as its +argument. If the callable returns True, training will terminate.

+
+

stdout:

+
+

An object with a write method that can be set to redirect +training status messages somewhere other than stdout. To +suppress output, set stdout to None.

+
+
+

Return value:

+
+

Returns True if desired accuracy was achieved.

+
+

Neural networks can require many iterations through a data set to +converge. If convergence slows (as indicated by small changes in +residual error), training can be terminated by pressing CTRL-C, which +will preserve the network weights from the previous training iteration. +train can then be called again with altered training parameters +(e.g., increased learning rate or momentum) to increase the convergence +rate.

+
+ +
+ +
+
+

Pixel Purity Index (PPI)

+
+
+ppi(X, niters, threshold=0, centered=False, start=None, display=0, **imshow_kwargs)
+

Returns pixel purity indices for an image.

+

Arguments:

+
+

X (ndarray):

+
+

Image data for which to calculate pixel purity indices

+
+

niters (int):

+
+

Number of iterations to perform. Each iteration corresponds to a +projection of the image data onto a random unit vector.

+
+

threshold (numeric):

+
+

If this value is zero, only the two most extreme pixels will have +their indices incremented for each random vector. If the value is +greater than zero, then all pixels whose projections onto the +random vector are with threshold data units of either of the two +extreme pixels will also have their indices incremented.

+
+

centered (bool):

+
+

If True, then the pixels in X are assumed to have their mean +already subtracted; otherwise, the mean of X will be computed +and subtracted prior to computing the purity indices.

+
+

start (ndarray):

+
+

An optional array of initial purity indices. This can be used to +continue computing PPI values after a previous call to ppi (i.e., +set start equal to the return value from a previou call to ppi. +This should be an integer-valued array whose dimensions are equal +to the first two dimensions of X.

+
+

display (integer):

+
+

If set to a postive integer, a ImageView +window will be opened and dynamically display PPI values as the +function iterates. The value specifies the number of PPI iterations +between display updates. It is recommended to use a value around +100 or higher. If the stretch keyword (see get_rgb +for meaning) is not provided, a default stretch of (0.99, 0.999) +is used.

+
+
+

Return value:

+
+

An ndarray of integers that represent the pixel purity indices of the +input image. The return array will have dimensions equal to the first +two dimensions of the input image.

+
+

Keyword Arguments:

+
+

Any keyword accepted by imshow. +These keywords will be passed to the image display and only have an +effect if the display argument is nonzero.

+
+

This function can be interruped with a KeyboardInterrupt (ctrl-C), in which +case, the most recent value of the PPI array will be returned. This can be +used in conjunction with the display argument to view the progression of +the PPI values until they appear stable, then terminate iteration using +ctrl-C.

+

References:

+

Boardman J.W., Kruse F.A, and Green R.O., “Mapping Target Signatures via +Partial Unmixing of AVIRIS Data,” Pasadena, California, USA, 23 Jan 1995, +URI: http://hdl.handle.net/2014/33635

+
+ +
+
+

principal_components

+
+
+principal_components(image)
+

Calculate Principal Component eigenvalues & eigenvectors for an image.

+

Usage:

+
pc = principal_components(image)
+
+
+

Arguments:

+
+

image (ndarray, spectral.Image, GaussianStats):

+
+

An MxNxB image

+
+
+

Returns a PrincipalComponents +object with the following members:

+
+

eigenvalues:

+
+

A length B array of eigenvalues

+
+

eigenvectors:

+
+

A BxB array of normalized eigenvectors

+
+

stats (GaussianStats):

+
+

A statistics object containing mean, cov, and nsamples.

+
+

transform:

+
+

A callable function to transform data to the space of the +principal components.

+
+

reduce:

+
+

A method to reduce the number of eigenvalues.

+
+

denoise:

+
+

A callable function to denoise data using a reduced set of +principal components.

+
+

get_denoising_transform:

+
+

A callable function that returns a function for denoising data.

+
+
+
+ +
+
+

PrincipalComponents

+
+
+class PrincipalComponents(vals, vecs, stats)
+

An object for storing a data set’s principal components. The +object has the following members:

+
+

eigenvalues:

+
+

A length B array of eigenvalues sorted in descending order

+
+

eigenvectors:

+
+

A BxB array of normalized eigenvectors (in columns)

+
+

stats (GaussianStats):

+
+

A statistics object containing mean, cov, and nsamples.

+
+

transform:

+
+

A callable function to transform data to the space of the +principal components.

+
+

reduce:

+
+

A method to return a reduced set of principal components based +on either a fixed number of components or a fraction of total +variance.

+
+

denoise:

+
+

A callable function to denoise data using a reduced set of +principal components.

+
+

get_denoising_transform:

+
+

A callable function that returns a function for denoising data.

+
+
+
+
+denoise(X, **kwargs)
+

Returns a de-noised version of X.

+

Arguments:

+
+

X (np.ndarray):

+
+

Data to be de-noised. Can be a single pixel or an image.

+
+
+

Keyword Arguments (one of the following must be specified):

+
+

num (integer):

+
+

Number of eigenvalues/eigenvectors to use. The top num +eigenvalues will be used.

+
+

eigs (list):

+
+

A list of indices of eigenvalues/eigenvectors to be used.

+
+

fraction (float):

+
+

The fraction of total image variance to retain. Eigenvalues +will be included (starting from greatest to smallest) until +fraction of total image variance is retained.

+
+
+

Returns denoised image data with same shape as X.

+

Note that calling this method is equivalent to calling the +get_denoising_transform method with same keyword and applying the +returned transform to X. If you only intend to denoise data with the +same parameters multiple times, then it is more efficient to get the +denoising transform and reuse it, rather than calling this method +multilple times.

+
+ +
+
+get_denoising_transform(**kwargs)
+

Returns a function for denoising image data.

+

Keyword Arguments (one of the following must be specified):

+
+

num (integer):

+
+

Number of eigenvalues/eigenvectors to use. The top num +eigenvalues will be used.

+
+

eigs (list):

+
+

A list of indices of eigenvalues/eigenvectors to be used.

+
+

fraction (float):

+
+

The fraction of total image variance to retain. Eigenvalues +will be included (starting from greatest to smallest) until +fraction of total image variance is retained.

+
+
+

Returns a callable LinearTransform +object for denoising image data.

+
+ +
+
+reduce(N=0, **kwargs)
+

Reduces the number of principal components.

+

Keyword Arguments (one of the following must be specified):

+
+

num (integer):

+
+

Number of eigenvalues/eigenvectors to retain. The top num +eigenvalues will be retained.

+
+

eigs (list):

+
+

A list of indices of eigenvalues/eigenvectors to be retained.

+
+

fraction (float):

+
+

The fraction of total image variance to retain. Eigenvalues +will be retained (starting from greatest to smallest) until +fraction of total image variance is retained.

+
+
+
+ +
+ +
+
+

RX Anomaly Detector

+
+
+rx(X, background=None, window=None, cov=None)
+

Computes RX anomaly detector scores.

+

Usage:

+
+

y = rx(X [, background=bg])

+

y = rx(X, window=(inner, outer) [, cov=C])

+
+

The RX anomaly detector produces a detection statistic equal to the +squared Mahalanobis distance of a spectrum from a background distribution +according to

+
+

y=(x-\mu_b)^T\Sigma^{-1}(x-\mu_b)

+

where x is the pixel spectrum, \mu_b is the background +mean, and \Sigma is the background covariance.

+

Arguments:

+
+

X (numpy.ndarray):

+
+

For the first calling method shown, X can be an image with +shape (R, C, B) or an ndarray of shape (R * C, B). If the +background keyword is given, it will be used for the image +background statistics; otherwise, background statistics will be +computed from X.

+

If the window keyword is given, X must be a 3-dimensional +array and background statistics will be computed for each point +in the image using a local window defined by the keyword.

+
+

background (GaussianStats):

+
+

The Gaussian statistics for the background (e.g., the result +of calling calc_stats). If no background stats are +provided, they will be estimated based on data passed to the +detector.

+
+

window (2-tuple of odd integers):

+
+

Must have the form (inner, outer), where the two values +specify the widths (in pixels) of inner and outer windows centered +about the pixel being evaulated. Both values must be odd integers. +The background mean and covariance will be estimated from pixels +in the outer window, excluding pixels within the inner window. For +example, if (inner, outer) = (5, 21), then the number of +pixels used to estimate background statistics will be +21^2 - 5^2 = 416.

+

The window are modified near image borders, where full, centered +windows cannot be created. The outer window will be shifted, as +needed, to ensure that the outer window still has height and width +outer (in this situation, the pixel being evaluated will not be +at the center of the outer window). The inner window will be +clipped, as needed, near image borders. For example, assume an +image with 145 rows and columns. If the window used is +(5, 21), then for the image pixel at (0, 0) (upper left corner), +the the inner window will cover image[:3, :3] and the outer +window will cover image[:21, :21]. For the pixel at (50, 1), the +inner window will cover image[48:53, :4] and the outer window +will cover image[40:51, :21].

+
+

cov (ndarray):

+
+

An optional covariance to use. If this parameter is given, cov +will be used for all RX calculations (background covariance +will not be recomputed in each window) and only the background +mean will be recomputed in each window.

+
+
+

Returns numpy.ndarray:

+
+

The return value will be the RX detector score (squared Mahalanobis +distance) for each pixel given. If X has shape (R, C, B), the +returned ndarray will have shape (R, C)..

+
+

References:

+

Reed, I.S. and Yu, X., “Adaptive multiple-band CFAR detection of an optical +pattern with unknown spectral distribution,” IEEE Trans. Acoust., +Speech, Signal Processing, vol. 38, pp. 1760-1770, Oct. 1990.

+
+ +
+
+

spectral_angles

+
+
+spectral_angles(data, members)
+

Calculates spectral angles with respect to given set of spectra.

+

Arguments:

+
+

data (numpy.ndarray or spectral.Image):

+
+

An MxNxB image for which spectral angles will be calculated.

+
+

members (numpy.ndarray):

+
+

CxB array of spectral endmembers.

+
+
+

Returns:

+
+

MxNxC array of spectral angles.

+
+

Calculates the spectral angles between each vector in data and each of the +endmembers. The output of this function (angles) can be used to classify +the data by minimum spectral angle by calling argmin(angles).

+
+ +
+
+

SpectralLibrary

+
+
+class SpectralLibrary(data, header=None, params=None)
+

The envi.SpectralLibrary class holds data contained in an ENVI-formatted +spectral library file (.sli files), which stores data as specified by a +corresponding .hdr header file. The primary members of an +Envi.SpectralLibrary object are:

+
+

spectra (numpy.ndarray):

+
+

A subscriptable array of all spectra in the library. spectra will +have shape CxB, where C is the number of spectra in the library +and B is the number of bands for each spectrum.

+
+

names (list of str):

+
+

A length-C list of names corresponding to the spectra.

+
+

bands (spectral.BandInfo):

+
+

Spectral bands associated with the library spectra.

+
+
+
+
+save(file_basename, description=None)
+

Saves the spectral library to a library file.

+

Arguments:

+
+

file_basename (str):

+
+

Name of the file (without extension) to save.

+
+

description (str):

+
+

Optional text description of the library.

+
+
+

This method creates two files: file_basename.hdr and +file_basename.sli.

+
+ +
+ +
+
+

transform_image

+
+
+transform_image(transform, img)
+

Applies a linear transform to an image.

+

Arguments:

+
+

transform (ndarray or LinearTransform):

+
+

The CxB linear transform to apply.

+
+

img (ndarray or spectral.SpyFile):

+
+

The MxNxB image to be transformed.

+
+
+

Returns (ndarray or :class:spectral.spyfile.TransformedImage`):

+
+

The transformed image.

+
+

If img is an ndarray, then a MxNxC ndarray is returned. If img is +a spectral.SpyFile, then a +spectral.spyfile.TransformedImage is returned.

+
+ +
+
+
+

Configuration

+
+

SpySettings

+
+
+class SpySettings
+

Run-time settings for the spectral module.

+

After importing spectral, the settings object is referenced as +spectral.settings.

+

Noteworthy members:

+
+

WX_GL_DEPTH_SIZE (integer, default 24):

+
+

Sets the depth (in number of bits) for the OpenGL depth buffer. +If calls to view_cube or view_nd result in windows with blank +canvases, try reducing this value.

+
+

envi_support_nonlowercase_params (bool, default False)

+
+

By default, ENVI headers are read with parameter names converted +to lower case. If this attribute is set to True, parameters will +be read with original capitalization retained.

+
+

show_progress (bool, default True):

+
+

Indicates whether long-running algorithms should display progress +to sys.stdout. It can be useful to set this value to False when +SPy is embedded in another application (e.g., IPython Notebook).

+
+

imshow_figure_size (2-tuple of integers, default None):

+
+

Width and height (in inches) of windows opened with imshow. If +this value is None, matplotlib’s default size is used.

+
+

imshow_background_color (3-tuple of integers, default (0,0,0)):

+
+

Default color to use for masked pixels in imshow displays.

+
+

imshow_interpolation (str, default None):

+
+

Pixel interpolation to be used in imshow windows. If this value +is None, matplotlib’s default interpolation is used. Note that +zoom windows always use “nearest” interpolation.

+
+

imshow_stretch:

+
+

Default RGB linear color stretch to perform.

+
+

imshow_stretch_all:

+
+

If True, each color channel limits are determined independently.

+
+

imshow_zoom_figure_width (int, default None):

+
+

Width of zoom windows opened from an imshow window. Since zoom +windows are always square, this is also the window height. If this +value is None, matplotlib’s default window size is used.

+
+

imshow_zoom_pixel_width (int, default 50):

+
+

Number of source image pixel rows and columns to display in a +zoom window.

+
+

imshow_float_cmap (str, default “gray”):

+
+

imshow color map to use with floating point arrays.

+
+

imshow_class_alpha (float, default 0.5):

+
+

alpha blending value to use for imshow class overlays

+
+

imshow_enable_rectangle_selector (bool, default True):

+
+

Whether to create the rectangle selection tool that enables +interactive image pixel class labeling. On some OS/backend +combinations, an exception may be raised when this object is +created so disabling it allows imshow windows to be created without +using the selector tool.

+
+

imshow_disable_mpl_callbacks (bool, default True):

+
+

If True, several matplotlib keypress event callbacks will be +disabled to prevent conflicts with callbacks from SPy. The +matplotlib callbacks can be set back to their defaults by +calling matplotlib.rcdefaults().

+
+
+
+ +
+
+
+

Utilities

+
+

iterator

+
+
+iterator(image, mask=None, index=None)
+

Returns an iterator over pixels in the image.

+

Arguments:

+
+

image (ndarray or spectral.Image):

+
+

An image over whose pixels will be iterated.

+
+

mask (ndarray) [default None]:

+
+

An array of integers that specify over which pixels in image +iteration should be performed.

+
+

index (int) [default None]:

+
+

Specifies which value in mask should be used for iteration.

+
+
+

Returns (spectral.Iterator):

+
+

An iterator over image pixels.

+
+

If neither mask nor index are defined, iteration is performed over all +pixels. If mask (but not index) is defined, iteration is performed +over all pixels for which mask is nonzero. If both mask and index +are defined, iteration is performed over all pixels image[i,j] for which +mask[i,j] == index.

+
+ +
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/fileio.html b/fileio.html new file mode 100644 index 0000000..71f7497 --- /dev/null +++ b/fileio.html @@ -0,0 +1,347 @@ + + + + + + + Reading HSI Data Files — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+

Reading HSI Data Files

+

The standard means of opening and accessing a hyperspectral image file with SPy +is via the image function, which returns an instance of a +SpyFile object.

+
+

The SpyFile Interface

+

SpyFile is the base class for creating objects to read +hyperspectral data files. When a SpyFile object is created, +it provides an interface to read data from a corresponding file. When an image +is opened, the actual object returned will be a subclass of +SpyFile (BipFile, BilFile, or BsqFile) corresponding to the +interleave of the data within the image file.

+

Let’s open our sample image.

+
In [1]: from spectral import *
+
+In [2]: img = open_image('92AV3C.lan')
+
+In [3]: img.__class__
+Out[3]: spectral.io.bilfile.BilFile
+
+In [4]: print(img)
+	Data Source:   '/home/thomas/spectral_data/92AV3C.lan'
+	# Rows:            145
+	# Samples:         145
+	# Bands:           220
+	Interleave:        BIL
+	Quantization:  16 bits
+	Data format:     int16
+

The image was not located in the working directory but it was still opened +because it was in a directory specified by the SPECTRAL_DATA environment +variable. Because the image pixel data are interleaved by line, the image +function returned a BilFile instance.

+

Since hyperspectral image files can be quite large, only +metadata are read from the file when the SpyFile object is +first created. Image data values are only read when specifically requested via +SpyFile methods. The SpyFile class +provides a subscript operator that behaves much like the numpy array subscript +operator. The SpyFile object is subscripted as an MxNxB +array where M is the number of rows in the image, N is the number of +columns, and B is thenumber of bands.

+
In [5]: img.shape
+Out[5]: (145, 145, 220)
+
+In [6]: pixel = img[50,100]
+
+In [7]: pixel.shape
+Out[7]: (220,)
+
+In [8]: band6 = img[:,:,5]
+
+In [9]: band6.shape
+Out[9]: (145, 145, 1)
+
+
+

The image data values were not read from the file until the subscript operator +calls were performed. Note that since Python indices start at 0, +img[50,100] refers to the pixel at 51st row and 101st column of the image. +Similarly, img[:,:,5] refers to all the rows and columns for the 6th band +of the image.

+

SpyFile subclass instances returned for particular image +files will also provide the following methods:

+ ++++ + + + + + + + + + + + + + + + + + + + + + + +

Method

Description

read_band

Reads a single band into an MxN array

read_bands

Reads multiple bands into an MxNxC array

read_pixel

Reads a single pixel into a length B array

read_subregion

Reads multiple bands from a rectangular sub-region of the image

read_subimage

Reads specified rows, columns, and bands

+

SpyFile objects have a bands member, which is an +instance of a BandInfo object that contains optional +information about the images spectral bands.

+
+
+

Loading Entire Images

+

It is important to note that image data are read by a SpyFile object on demand +and the data are not cached. Each time the SpyFile subscript operator or one of +the SpyFile read methods are called, data are read from the corresponding image +data file, regardless of whether the same data have been previously read. This +is done to avoid consuming too much memory when working with very large image files. +It also improves performance when performing operations that only require reading +a small portion of the data in a large image (e.g., reading RGB bands to display +the image). The downside of reading data on demand and not caching the data is that there can +be a significant run time penalty when running algorithms that require access to +all of the data. Performance will be even worse if the algorithm requires iterative +access to the data.

+

To improve performance of spectral algorithms, it is preferable to load the entire +image into memory using the load method, which returns +an ImageArray object. ImageArray provides +the full numpy.ndarray interface, as well as the SpyFile interface.

+
In [1]: arr = img.load()
+
+In [2]: arr.__class__
+Out[2]: spectral.image.ImageArray
+
+In [3]: print(arr.info())
+	# Rows:            145
+	# Samples:         145
+	# Bands:           220
+	Data format:   float32
+
+In [4]: arr.shape
+Out[4]: (145, 145, 220)
+

Because SPy is primarily designed for processing in the spectral domain, +spectral.ImageArray objects in memory will always have data interleaved +by pixel, regardless of the interleave of the source image data file. In other +words, the numpy.ndarray shape will be (numRows, numCols, numBands). +ImageArray objects always contain 32-bit floats.

+
+

Note

+

Before calling the load method, it is important to consider the amount of memory +that will be consumed by the resulting ImageArray object. Since spectral.ImageArray uses +32-bit floating point values, the amount of memory consumed will be approximately +4 * numRows * numCols * numBands bytes.

+
+
+
+

NumPy memmap Interface

+

As an alternative to loading an entire image into memory, a somewhat slower +(but more memory efficient) way to access image data is to use a numpy memmap +object, as returned by the open_memmap +method of SpyFile objects. memmap objects can also be used to write date to +an image file.

+
+
+

File Formats Supported

+
+

ENVI Headers

+

ENVI 1 is a popular commercial software package for processing and analyzing +geospatial imagery. SPy can read images that have associated ENVI header files +and can read & write spectral libraries with ENVI headers. ENVI files are opened +automatically by the SPy image function but images can also be +opened explicitly as ENVI files. It may be necessary to open an ENVI file explicitly +if the data file is in a separate directory from the header or if the data file +has an unusual file extension that SPy can not identify.

+
In [5]: import spectral.io.envi as envi
+
+In [6]: img = envi.open('cup95eff.int.hdr', 'cup95eff.int')
+
+In [7]: import spectral.io.envi as envi
+
+In [8]: lib = envi.open('spectra.hdr')
+
+In [9]: lib.names[:5]
+Out[9]: 
+['construction asphalt',
+ 'construction  concrete',
+ 'red smooth-faced brick',
+ 'weathered red brick',
+ 'bare red brick']
+
+
+
+

See also

+

Functions for writing image data to files:

+

create_image:

+
+

Creates a new image file with allocated storage on disk.

+
+

save_image:

+
+

Saves an existing image or ndarray to a file with an ENVI header.

+
+
+
+
+

AVIRIS

+

SPy supports data files generated by the Airborne Visible/Infrared Imaging +Spectrometer (AVIRIS) 2. AVIRIS files are automatically recognized by +the open_image function; however, spectral band calibration files +are not automatically recognized; therefore you may want to open the image as +an AVIRIS file explicitly and specify the cal file.

+
In [10]: img = aviris.open('f970619t01p02_r02_sc01.a.rfl', 'f970619t01p02_r02.a.spc')
+
+
+

You can also load the band calibration file separately (this may be necessary if +the band calibration file is in AVIRIS format but the image is not).

+
In [11]: img = open_image('92AV3C.lan')
+
+In [12]: img.bands = aviris.read_aviris_bands('92AV3C.spc')
+
+
+
+
+

ERDAS/Lan

+

The ERDAS/Lan file format is automatically recognized by image. +It is unlikely that a file would need to be opened explicitly as a Lan file but it +can be done as follows.

+
In [13]: import spectral.io.erdas as erdas
+
+In [14]: img = erdas.open('92AV3C.lan')
+
+
+
+
1
+

ENVI is a registered trademark of Exelis Visual Information Solutions.

+
+
2
+

http://aviris.jpl.nasa.gov/

+
+
+
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/graphics.html b/graphics.html new file mode 100644 index 0000000..f134dac --- /dev/null +++ b/graphics.html @@ -0,0 +1,551 @@ + + + + + + + Displaying Data — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+

Displaying Data

+
+

Starting IPython

+

SPy uses IPython to provide GUI windows without blocking the interactive python +interpreter. To enable this, you must start IPython in “pylab” mode. If you +have already set your matplotlib backend +to either “WX” or “WXAgg” (see note below) in your matplotlibrc file, you +should be able to start IPython for SPy like this:

+
ipython --pylab
+
+
+

You can also set the backend explicitly when starting IPython this way:

+
ipython --pylab=wx
+
+
+
+

Note

+

If you are not calling GUI functions (calling save_rgb +doesn’t count as a GUI function), then it is not necessary to run IPython - +you can run the standard python interpreter.

+
+
+

Note

+

If you are not able to run the WX backend on your system, you can still use +a different backend (e.g., Qt4Agg or TkAgg); however you will be unable +to call the view_cube or +view_nd functions.

+
+
+
+

Raster Displays

+

The SPy imshow function is a wrapper around +the matplotlib function of the same name. The main differences are that the SPy +version makes it easy to display bands from multispectral/hyperspectral images, +it renders classification images, and supports several additional types of +interactivity.

+
+

Image Data Display

+

The imshow function produces a raster display of data associated with an +np.ndarray or SpyFile object.

+
In [1]: from spectral import *
+
+In [2]: img = open_image('92AV3C.lan')
+
+In [3]: view = imshow(img, (29, 19, 9))
+
+
+_images/imshow_92AV3C_rgb.png +

When displaying the image interactively, the matplotlib button controls can be +used to pan and zoom the displayed images. If you press the “z” keyboard key, +a zoom window will be opened, which displays a magnified view of the image. By +holding down the CONTROL key and left-clicking in the original window, the +zoom window will pan to the pixel clicked in the original window.

+
+

Changed in version 0.16.0: By default, imshow function applies a linear histogram stretch of the RGB +display data. The the color stretch can be controlled by the stretch, +bounds, and stretch_all keyword to the imshow function (see +get_rgb for the meaning of these +keywords). To adjust the color stretch of a displayed image, the +set_rgb_options method of the +ImageView object can be called.

+
+

RGB data limits for a displayed image can be printed from the __str__ method +of the ImageView object:

+
In [4]: print(view)
+ImageView object:
+  Display bands       :  (29, 19, 9)
+  Interpolation       :  <default>
+  RGB data limits     :
+    R: [2054.0, 6317.0]
+    G: [2775.0, 7307.0]
+    B: [3560.0, 7928.0]
+
+
+
+
+

Class Map Display

+

To display the ground truth image using imshow, set the classes +argument in the imshow function:

+
In [5]: gt = open_image('92AV3GT.GIS').read_band(0)
+
+In [6]: view = imshow(classes=gt)
+
+
+_images/imshow_92AV3C_gt.png +

It is also possible to switch between displaying image bands and class colors, +as well as displaying class color masks overlayed on the image data display. To +do this, specify both the data and class values when calling imshow:

+
In [7]: view = imshow(img, (30, 20, 10), classes=gt)
+
+
+

The default display mode is to show the image bands. Press “d”, “c”, +or “C” (while focus is on the image window) to switch the display to data, +classes, or class overlay, respectively. Setting the display parameters can also +be done programatically. For example, to display the image with overlayed class +masks, using an alpha transparency of 0.5, type the following commands after +calling imshow:

+
In [8]: view = imshow(img, (30, 20, 10), classes=gt)
+
+In [9]: view.set_display_mode('overlay')
+
+In [10]: view.class_alpha = 0.5
+
+
+
+_images/imshow_92AV3C_overlay.png +
+
+
+

Interactive Class Labeling

+

The ImageView window provides the ability to modify pixel class IDs interactively +by selecting rectangular regions in the image and assigning a new class ID. +Applying a class ID to a rectangular region is done using the following steps:

+
+
    +
  1. Call imshow, providing an initial array for the classes argument. +This must be a non-negative, integer-valued array. A class ID value of zero +represents an unlabeled pixel so to start with a completely unlabeled +image, pass an array of all zeros for the classes argument.

  2. +
  3. While holding the SHIFT key, click with the left mouse button at the +upper left corner of the rectangle to be selected. Drag the mouse cursor +and release the mouse button at the lower right corner of the rectangular +region. Note that releasing the SHIFT key before the mouse button is +released will result in cancellation of the selection operation.

  4. +
  5. With focus still on the ImageView window, enter the numeric +class ID to apply to the region. The class ID can contain multiple digits. +The digits will not be echoed on the command line. Press ENTER to +apply the class ID. The command line will request confirmation of the +opertion. Press ENTER again to apply the class ID or press any other +key to cancel the operation.

  6. +
+
+

Classes can be assigned form either a main ImageView window or from +an associated zoom window. While the selection tool only produces rectangular +regions, you can assign classes to non-rectangular regions by first assigning +the class ID to a super-rectangle covering all the pixels of interest, then +reassigning sub-rectangles back to class 0 (or whatever was the original class ID).

+
+
+

Additional Capabilities

+

To get help on ImageView keyboard & mouse functions, press “h” while focus is on +the image window to display all keybinds and mouse functions:

+
Mouse Functions:
+----------------
+ctrl+left-click          ->   pan zoom window to pixel
+shift+left-click&drag    ->   select rectangular image region
+left-dblclick            ->   plot pixel spectrum
+
+Keybinds:
+---------
+0-9     -> enter class ID for image pixel labeling
+ENTER   -> apply specified class ID to selected rectangular region
+a/A     -> decrease/increase class overlay alpha value
+c       -> set display mode to "classes" (if classes set)
+C       -> set display mode to "overlay" (if data and classes set)
+d       -> set display mode to "data" (if data set)
+h       -> print help message
+i       -> toggle pixel interpolation between "nearest" and SPy default.
+z       -> open zoom window
+
+See matplotlib imshow documentation for addition key binds.
+
+
+

To customize behavior of the image display beyond what is provided by the +imshow function, you can create an +ImageView object directly and customize it +prior to calling its show method. You can also access the +matplotlib figure and canvas attributes from the axes +attribute of the ImageView object returned by imshow. Finally, +you can customize some of the default behaviours of the image display by +modifiying members of the module’s SpySettings +object.

+
+
+
+

Saving RGB Image Files

+

To save an image display to a file, use the save_rgb function, +which uses the same arguments as imshow but with the saved image +file name as the first argument.

+
In [11]: save_rgb('rgb.jpg', img, [29, 19, 9])
+
+
+

Saving an indexed color image is similar to saving an RGB image; however, save_rgb +is unable to determine if the image being saved is a single-band (greyscale) image +or an indexed color image. Therefore, to save as an indexed color image, the color +palette must be explicitly passed as a keyword argument:

+
In [12]: save_rgb('gt.jpg', gt, colors=spy_colors)
+
+
+
+
+

Spectrum Plots

+

The image display windows provide a few interactive functions. If you create an +image display with imshow (or view) +and then double-click on a particular location in the window, a new window will +be created with a 2D plot of the spectrum for the pixel that was clicked. It +should look something like this:

+
+_images/rgb_double_click.png +
+

Note that the row/col of the double-clicked pixel is printed on the command prompt. +Since there is no spectral band metadata in our sample image file, the spectral +plot’s axes are unlabeled and the pixel band values are plotted vs. band number, +rather than wavelength. To have the data plotted vs. wavelength, we must first +associated spectral band information with the image.

+
In [13]: import spectral.io.aviris as aviris
+
+In [14]: img.bands = aviris.read_aviris_bands('92AV3C.spc')
+
+
+

Now, close the image and spectral plot windows, call view +again and click on a few locations in the image display. You will notice that +the x-axis now shows the wavelengths associated with each band.

+
+_images/rgb_with_bands_double_click.png +
+

Notice that the spectra are now plotted against their associated wavelengths.

+

The spectral plots are intended as a convenience to enable one to quickly view +spectra from an image. If you would like a prettier plot of the same data, you +can use SPy to read the spectra from the image and create a customized plot by +using matplotlib directly.

+
+
+

Hypercube Display

+

In the context of hyperspectral imagery, a 3D hypercube is a 3-dimensional +representation of a hyperspectral image where the x and y dimensions are the spatial +dimensions of the image and the third dimension is the spectral dimension. The +analytical utility of hypercube displays is debatable but what is not debatable +is that they look extremely cool.

+

After calling view_cube, +a new window will open with the 3D hypercube displayed. After shifting +command focus to the newly created window, you can use keyboard input to alter +the view of the hypercube. Using keyword arguments, you can change the image +displayed on the top of the cube, as well as the color scale used on the sides of +of the cube.

+
In [15]: view_cube(img, bands=[29, 19, 9])
+
+
+
Mouse Functions:
+----------------
+left-click & drag        ->   Rotate cube
+CTRL+left-click & drag   ->   Zoom in/out
+SHIFT+left-click & drag  ->  Pan
+
+Keybinds:
+---------
+l       -> toggle light
+t/g     -> stretch/compress z-dimension
+h       -> print help message
+q       -> close window
+
+
+
+_images/hypercube.jpg +
+
+

Note

+

If the window opened by view_cube produces a blank canvas +(no cube is displayed), it may be due to your display adapter not supporting +a 32-bit depth buffer. You can reduce the size of the depth buffer used by +view_cube and view_nd to a smaller value +(e.g., 16) by issuing the following commands:

+
+
In [16]: import spectral
+
+In [17]: spectral.settings.WX_GL_DEPTH_SIZE = 16
+
+
+
+
+

N-Dimensional Feature Display

+

Since hyperspectral images contain hundreds of narrow, contiguous bands, there is often +strong correlation between bands (particularly adjacent bands). To increase the +amount of information displayed, it is common to reduce the dimensionality of +the image to a smaller set of features with higher information density (e.g., by +principal components transformation). Nevertheless, there are typically still +many more than three features remaining in the transformed image so the analyst +must decide which are the “best” three features to display to highlight some +aspect of the data set (e.g., separability of the spectral classes). It is +desirable to be able to quickly switch displayed features to examine many +combinations of features in a short time span.

+

In most cases, the display will be more useful by first performing +dimensionality reduction prior to viewint the data (e.g., by selecting some +number of principal components).

+
In [18]: data = open_image('92AV3C.lan').load()
+
+In [19]: gt = open_image('92AV3GT.GIS').read_band(0)
+
+In [20]: pc = principal_components(data)
+Covariance.....done
+
+In [21]: xdata = pc.transform(data)
+
+In [22]: w = view_nd(xdata[:,:,:15], classes=gt)
+
+
+

A new window should open and appear as follows:

+
+_images/ndwindow_orig.png +

Initial view of the N-Dimensinal window

+
+

The python prompt will display the set of keyboard and mouse commands accepted +by the ND window:

+
Mouse functions:
+---------------
+Left-click & drag       -->     Rotate viewing geometry (or pan)
+CTRL+Left-click & drag  -->     Zoom viewing geometry
+CTRL+SHIFT+Left-click   -->     Print image row/col and class of selected pixel
+SHIFT+Left-click & drag -->     Define selection box in the window
+Right-click             -->     Open GLUT menu for pixel reassignment
+
+Keyboard functions:
+-------------------
+a       -->     Toggle axis display
+c       -->     View dynamic raster image of class values
+d       -->     Cycle display mode between single-quadrant, mirrored octants,
+                and independent octants (display will not change until features
+                are randomzed again)
+f       -->     Randomize features displayed
+h       -->     Print this help message
+m       -->     Toggle mouse function between rotate/zoom and pan modes
+p/P     -->     Increase/Decrease the size of displayed points
+q       -->     Exit the application
+r       -->     Reset viewing geometry
+u       -->     Toggle display of unassigned points (points with class == 0)
+
+
+

The following figure shows the same window after the display has been rotated, +unlabeled pixels (white) have been supressed, and the displayed pixel size has +been increased:

+
+_images/ndwindow_mod.png +

Modified view of the N-Dimensinal window

+
+
+

Display Modes

+

To facilitate interactive visual analysis of high-dimensional images, the +view_nd function provides several modes for +displaying high-dimensional data in a 3-dimensional display window:

+
    +
  1. Single-Octant Mode - In this mode, three features from the image are selected +and mapped to the x, y, and z axes in the +3D window. The data are translated and scaled such that the displayed points +fill the positive x/y/z octant of the 3D space. The user can choose to +randomly select a new set of three features to display. With this randomization +capability, the user can rapidly cycle through various combinations of features +to identify desirable projections.

  2. +
  3. Mirrored-Octant Mode - In this mode, three features are mapped to the +positive x, y, and z axes as in the single-octant mode. However, an +additional three features are selected and mapped the the negative portions +of the x, y, and z axes. The image pixels are then displayed in each of the eight +octants of the 3D display, using whichever features are associated with the +three semi-axes for the particular octant. In this mode, each partial plane +defined by two semi-axes can be considered a mirror, with the two +perpendicular semi-axes representing different features. For example, if the +x, y, z, -x, -y, and -z semi-axes are associated with features 1 through 6, +respectively, then the data points in the x/y/z and x/y/-z quadrants will +have a common projection onto the x/y plane. However, since z and -z are +mapped to different features, the data points will have different projections +along the z and -z axes. As with the single-octant mode, the user can +randomize the 6 features displayed.

  4. +
  5. Independent-Octant Mode - In this mode, each octant of the 3D space represents +3 independent features, enabling up to 24 features to be displayed. As with +the other two modes, the data points are translated and scaled such that the +entire set of image pixels completely fills each octant. It is possible that +a particular feature may be used in more than one octant.

  6. +
+

The user has the option of switching between display modes. Which display mode +is best may depend on a number of factors relating to the data set used or the +system on which the application is run. For images with sufficiently large +numbers of pixels, the system’s display refresh rate may begin to suffer in +mirrored-octant or independent-octant mode because each image pixel must be +rendered 8 times. When performance is not an issue, the latter two modes provide +more simultaneous projections of the data that may enable a user to more quickly +explore the data set.

+
+
+

User Interaction

+

The initial 3D view of the rendered image data will almost certainly not be the +ideal 3D viewing geometry; therefore, the window allows the user to +manipulate the view by using the mouse input to zoom, pan, and rotate +the viewing geometry. The user can also increase/decrease the size of displayed +image pixels and toggle display of unlabeled (class 0) pixels.

+

While pixel color allows the user to distinguish between classes of displayed +pixels, it may not be obvious to which class a given color is associated. To +identify the class of a specific pixel, the user can click on a pixel and the +python command line will print the pixel’s location within the image +(row and column values) and the number (ID) of the class of the pixel. Since +clicking on a single screen pixel is difficult, the user can first increase the +size of the displayed image pixels such that the image pixel can easily be +selected with the mouse.

+
+
+

Data Manipulation

+

It is common for an analyst to want to review the results of unsupervised +classification and subsequently combine pixel classes or reassign pixels to new +or different classes. Similarly, an analyst may want to assess ground truth data and +possibly reassign pixels that were erroneously included in a class when the +ground truth image was developed. To support this capability, the N-D window +allows the user to click and drag with the mouse to select a rectangular region +and then reassign all pixels lying within the defined box. The application will +not only reassign the pixels visible within the selection box but also all pixels +within the projection of the box through the 3D space.

+

When view_nd is called, it returns a proxy +object that has a classes member that represents the current array of class +values associated with the ND display. The proxy object can be used to access +the updated class array after image pixels have been reassigned through the ND +window. The proxy object also has a set_features method to specify the +set of features displayed in the 3D window.

+
+

Tip

+

Before reassigning pixel class values, press the “c” key to open a second +window displaying a raster view of the current image pixel class values. Then, when +you reassign pixel classes in the ND window, the class raster view will +update automatically, providing and indication of which pixels in the source +image were modified.

+
+
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..0c130b0 --- /dev/null +++ b/index.html @@ -0,0 +1,287 @@ + + + + + + + Welcome to Spectral Python (SPy) — Spectral Python 0.21 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+

Welcome to Spectral Python (SPy)

+
+_images/hypercube_big.jpg +
+

Spectral Python (SPy) is a pure Python module for processing hyperspectral image +data. It has functions for reading, displaying, manipulating, and classifying +hyperspectral imagery. It can be used interactively from the Python command +prompt or via Python scripts. SPy is free, Open Source software distributed +under the MIT License. +To see some examples of how SPy can be used, you may want to jump straight to +the documentation sections on Displaying Data or Spectral Algorithms. A +categorized listing of the main classes and functions are in the +Class/Function Glossary. You can download SPy from +GitHub +or the Python Package Index (PyPI). +See the Installing SPy section section of the documentation for details.

+
+ +
+

News

+

2020-04-26 : As of version 0.21, Spectal Python (SPy) is released under the MIT License.

+

2019-10-06 : SPy 0.20 adds support for the ECOSTRESS spectral library.

+

2017-06-04 : SPy 0.19 provides plotting support for bad band lists and adds a few utility methods.

+

2016-06-18 : SPy 0.18 fixes several bugs and has improved ENVI header support.

+

2015-11-11 : SPy 0.17 enables mapping class labels between images.

+
+

Class labels can be mapped between images (e.g., from an unsupervised +classification result to a ground truth image) using map_class_ids +and map_classes. ENVI file handling +is improved, view_nd image windows support arbitrary +axis labels, and SpyFile objects have improved numpy ndarray +interfaces. See the SPy 0.17 release notes +for details. And thanks to Don March for +many contributions to this release.

+
+

2014-10-18 : SPy 0.16.0 provides initial support for Python 3.

+
+

This release supports Python 3 for all functions except view_cube +and view_nd. Note that for Python 3, you should use the +Qt4Agg matplotlib backend.

+

New features in this release include the Adaptive Coherence/Cosine Esimator (ace) +target detector, Pixel Purity Index (ppi), +ability to save ENVI classification files (envi.save_classification), +and linear contrast enhancement (by data limits or cumulative histogram percentiles). +The SPy imshow function now applies +a 2% histogram color stretch by default (this can be overridden in the +spectral.settings object).

+

Additional info is in the version 0.16.0 issues.

+
+

2014-06-04 : SPy 0.15.0 is released.

+
+

This version adds the Minimum Noise Fraction algorithm +(mnf) +(a.k.a., Noise-Adjusted Principal Components). The related function +noise_from_diffs performs +estimation of image noise from a spectrally homogeneous region of the +image.

+

SpyFile read methods now accept an optional use_memmap argument that +provides finer control over when to use (or not use) the memmap interface +to read image file data.

+

Many thanks to Don March (http://ohspite.net) for improving +ENVI header support (comment lines and blank parameters are now accepted) +and providing several performance improvements.

+

Additional details are here.

+
+

2014-03-02 : SPy has moved!

+
+

The Spectral Python web site is now located at www.spectralpython.net. +All old URLs will automatically redirect to the new site. The primary source code +repository has also moved and is now hosted on GitHub at +https://github.com/spectralpython/spectral. +For the indefinite future, source code and release builds will continue to +be mirrored on Sourceforge.net and as always, the current release can +always be installed from the Python Package Index (PyPI) +using pip.

+
+

2014-02-23 : SPy 0.14 is released. This is primarily a bug fix release.

+
+

This release fixes a bug in PerceptronClassifier +and provides significant performance improvement. A bug is also fixed that +causes incorrect display of several faces in the view_cube +image cube display. See VERSIONS.txt file for full details.

+
+

2014-01-06 : Numerous new user interface features and performance improvements in SPy 0.13.

+
+

The SPy imshow wrapper around matplotlib’s +imshow function provides numerous new features, including:

+
+
    +
  • Interactive image class labeling using keyboard & mouse

  • +
  • Zoom windows

  • +
  • Class overlays with adjustable transparency

  • +
  • Dynamic view of changing pixel classes when modified in an ND Window.

  • +
+
+

Data/Statistic cacheing and more efficient use of numpy provides significant +performance improvement in mutiple algorithms (GMLC 14x, Mahalanobis +classifier 8x, kmeans 3x). Functions rx +and matched_filter are significantly +faster, particularly when using common global covariance.

+

The cov_avg function computes +covariance averaged over a set of classes (useful when samples are limited +or global covariance is desired). Christian Mielke provided code for the +msam function, which computes the +Modified SAM score (by Oshigami et al).

+
+

2013-09-06 : SPy 0.12 is released.

+
+

SPy 0.12 provides an improved memmap interface that enables accessing image +data using arbitrary interleaves and supports editable images (see +open_memmap for details). The RX +anomaly detector (rx) now allows +anomaly detection using local (sub-image) statistics by specifying an inner/outer +window around each pixel. The ability to disable algorithm progress messages +and addition of a wrapper around matplotlib’s imshow function are provided to +simplify integration of SPy code with IPython Notebooks.

+
+

2013-04-03 : SPy 0.11 is released.

+
+

This release adds an RX anomaly detector, +ability to save and create images in ENVI format (see save_image +and create_image), and a unit-testing sub-package. +The top-level namespace has been simplified and several functions have been +renamed for consistency (image is now open_image +and save_image is now save_rgb).

+
+

2013-02-23 : SPy 0.10.1 bug-fix release is now available.

+
+

This is a bug-fix release that corrects the spectrum displayed when double- +clicking on a raster display. Version 0.10 introduced a bug that had the +row/column swapped, resulting in either the wrong pixel being plotted or an +exception raised.

+

If you have installed SPy 0.10, you should install this update as soon as +possible.

+
+

2013-02-17 : SPy 0.10 is released: SPy now uses IPython for GUI display.

+
+

As of this release, SPy now uses IPython for non-blocking GUI windows. IPython +should be started in pylab mode with the appropriate backend set (see +Starting IPython). The standard python interpreter can still be used if +GUI functions are not being called. This release also resolves a number of +issues associated with different versions of wxWidgets (2.8.x vs. 2.9.x) on +various operating systems.

+
+

2013-01-23 : SPy 0.9 adds a linear matched filter target detector.

+
+

MatchedFilter uses background and target means, along +with background covariance to provide a linear target detector.

+

A generic LinearTransform class allows simple application +of linear transforms to numpy ndarrays or SpyFile objects.

+
+

2012-07-10 : SPy 0.8 adds N-Dimensional visualization of hyperspectral image data.

+
+

The ndwindow function enables viewing of +high-dimensional images in a 3D display. See N-Dimensional Feature Display for details.

+

Hypercube display now uses mouse control for pan/zoom/rotate.

+

Fixed a bug in several deprecation warnings that caused infinte recursion.

+
+

2012-02-19 : SPy 0.7 Released.

+
+

The kmeans algorithm is about 10 times faster than version 0.6. Many +method/function names have been renamed for consistency with external packages. +A few bugs potentially affecting BIP and BSQ input have been fixed.

+
+

2011-01-17 : SPy 0.6 Released.

+
+

This release adds ASTER Spectral Library support, ability to save spectral +libraries, and installation via distutils.

+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/installation.html b/installation.html new file mode 100644 index 0000000..78ea07b --- /dev/null +++ b/installation.html @@ -0,0 +1,226 @@ + + + + + + + Installing SPy — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Installing SPy

+
+
+
+

SPy Dependencies

+

SPy requires Python and depends on several other freely available Python +modules. Prior to installing SPy, you should make sure its dependencies are met. +While you can use SPy to process hyperspectral data with just Python and NumPy, +there are several other modules you will need if you want to use any of SPy’s +graphical capabilities.

+ + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SPy Dependencies

Dependency

Notes

Python 2.6+ or 3.3+

(1)

NumPy

Required

Pillow or Python Imaging Library (PIL)

Required if displaying or saving images

wxPython

(2)

matplotlib

Required if rendering raster displays or spectral plots

IPython

Required for interactive, non-blocking GUI windows

PyOpenGL

(2)

+

Notes:

+

(1): All SPy functions work with Python 3.3+ except view_cube and view_nd.

+

(2): Required if calling view_cube or view_nd.

+

As of SPy version 0.10, IPython is used to provide interactive GUI windows. +To use SPy with IPython, you will need to start IPython in “pylab” mode +(see Starting IPython).

+
+
+

Installing from a distribution package

+

SPy is distributed as a Python source distribution, which can be downloaded from +the Python Package Index (PyPI) or +from the SPy Project Page on +GitHub. The source distribution will unpack to a directory with +a name like spectral-x.y, where x.y is the SPy version number. To +install SPy, open a console in the unpacked directory and type the following:

+
python setup.py install
+
+
+

Note that if you are on a unix-based system, you will either need to be logged +in as root or preface the above command with “sudo” (unless you use the -d +option to install it to a local directory).

+
+
+

Installing with pip or Distribute

+

If you have Distribute (or the +deprecated Setuptools) or pip installed on +your system, there is no need to download the latest SPy version explicitly. +If you have Distribute installed, simply type

+
easy_install spectral
+
+
+

or using pip, type

+
pip install spectral
+
+
+

Note that your pip binary may be named differently (e.g., “pip-python”). And +again, you may need to be logged in as root or use “sudo” if you are on a +unix-based system.

+
+
+

Installing from the Git source code repository

+

The latest version of the SPy source code resides in the GitHub source code +repository. While the latest source code may be less stable than the most +recent release, it often has newer features and bug fixes. To download the +latest version of SPy from the Git repository, cd to the directory +where you would like to check out the source code and type the following:

+
git clone https://github.com/spectralpython/spectral.git
+
+
+
+
+

Configuring SPy

+

Because hyperspectral data files can be quite large, you might store all your +HSI data in one or several specific directories. To avoid having to type +absolute path names whenever you attempt to open an HSI file in SPy, you can +define a SPECTRAL_DATA environment variable, which SPy will use to find +image files (if they are not found in the current directory). SPECTRAL_DATA +should be a colon-delimited list of directory paths.

+
+

See also

+

SpySettings

+
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/libraries.html b/libraries.html new file mode 100644 index 0000000..74d197b --- /dev/null +++ b/libraries.html @@ -0,0 +1,345 @@ + + + + + + + Spectral Libraries — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Spectral Libraries

+
+
+
+

ECOSTRESS Spectral Library

+

The ECOSTRESS spectral library provides over 3,000 high-resolution spectra for +numerous classes of materials [Meerdink2019]. The spectra and associated metadata +are provided as a large set of ASCII text files. SPy provides the ability to +import the ECOSTRESS library spectra and a subset of the associated metadata into +a relational databased that can be easily accessed from Python.

+

You will first need to get a copy of the ECOSTRESS spectral library data files, +which can be requested here. Once you have +acquired the library data, you can import the data in to an sqlite +database as follows:

+
In [1]: db = spy.EcostressDatabase.create('ecostress.db', './eco_data_ver1')
+Importing ./eco_data_ver1/vegetation.tree.betula.lenta.tir.bele-1-55.ucsb.nicolet.spectrum.txt.
+Importing ./eco_data_ver1/vegetation.tree.quercus.lobata.tir.vh282.ucsb.nicolet.spectrum.txt.
+Importing ./eco_data_ver1/nonphotosyntheticvegetation.branches.adenostoma.fasciculatum.vswir.vh334.ucsb.asd.spectrum.txt.
+Importing ./eco_data_ver1/mineral.silicate.cyclosilicate.fine.tir.cs-2a.jpl.nicolet.spectrum.txt.
+Importing ./eco_data_ver1/mineral.hydroxide.none.fine.tir.goethite_1.jhu.nicolet.spectrum.txt.
+---// snip //---
+Importing ./eco_data_ver1/mineral.silicate.inosilicate.coarse.vswir.in-8a.jpl.perkin.spectrum.txt.
+Importing ./eco_data_ver1/vegetation.shrub.aloe.arborescens.tir.jpl077.jpl.nicolet.spectrum.txt.
+Importing ./eco_data_ver1/mineral.silicate.phyllosilicate.coarse.tir.ps-12f.jpl.nicolet.spectrum.txt.
+Processed 3403 files.
+
+
+
+

Note

+

The ECOSTRESS library supercedes the older ASTER spectral library +[Baldridge2009]. To import data from version 2.0 of the ASTER library, +follow the precedure above but with AsterDatabase instead of +EcostressDatabase.

+
+

Once the database has been created, it can be accessed by instantiating an +EcostressDatabase object for the database file (it can also be +accessed by using sqlite external to Python). The current implementation of the +SPy database contains two tables: Samples and Spectra. There is a one-to-one +relationship between rows in the two tables but they have been separate to support +potential future changes to the database. The schemas for the tables are in the +schemas attribute of the database object:

+
In [2]: db = EcostressDatabase('ecostress.db')
+
+In [3]: for s in db.schemas:
+   ...:     print(s)
+   ...: 
+CREATE TABLE Samples (SampleID INTEGER PRIMARY KEY, Name TEXT, Type TEXT, Class TEXT, SubClass TEXT, ParticleSize TEXT, SampleNum TEXT, Owner TEXT, Origin TEXT, Phase TEXT, Description TEXT)
+CREATE TABLE Spectra (SpectrumID INTEGER PRIMARY KEY, SampleID INTEGER, SensorCalibrationID INTEGER, Instrument TEXT, Environment TEXT, Measurement TEXT, XUnit TEXT, YUnit TEXT, MinWavelength FLOAT, MaxWavelength FLOAT, NumValues INTEGER, XData BLOB, YData BLOB)
+
+
+

Descriptions of most of the Sample table columns can be found in the ECOSTRESS +Spectral Library documentation. The sample spectra and bands are stored in +BLOB objects in the database, so you probably don’t want to access them directly. +The recommended method is to use either the get_spectrum +or get_signature method of EcostressDatabase, +both of which take a SpectrumID as their argument.

+

As a simple example, suppose you want to find all samples that have “stone” in +their name (this isn’t the best way to find stones in the library but it makes +for an easy example). There are three ways you can issue queries through the +EcostressDatabase object. You can call its print_query +method to print query results to the command line. You can call its +query method to return query results in +tuples. Lastly, you can use the cursor attribute of the EcostressDatabase +object to issue the query.

+
In [4]: db.print_query('SELECT COUNT() FROM Samples WHERE Name LIKE "%stone%"')
+78
+
+In [5]: db.print_query('SELECT SampleID, Name FROM Samples WHERE Name LIKE "%stone%" limit 10')
+14|Limestone Breccia
+73|Argillaceous Limestone
+167|Arkosic Sandstone
+171|Limestone CaCO3
+203|Limestone CaCO3
+237|Greywacke Sandstone
+275|Gray/dark brown extremely stoney coarse sandy
+279|Sandstone (Red)
+280|Gray Sandstone
+384|Dolomitic Limestone
+

Next, let’s retrieve and plot one of the results (we will take the last one).

+
In [6]: f = plt.figure()
+
+In [7]: s = db.get_signature(384)
+
+In [8]: import pylab as plt
+
+In [9]: plt.plot(s.x, s.y)
+Out[9]: [<matplotlib.lines.Line2D at 0x7f61aab7dcc0>]
+
+In [10]: plt.title(s.sample_name)
+Out[10]: Text(0.5, 1, 'Dolomitic Limestone')
+
+In [11]: plt.grid(1)
+_images/limestone.png +
+

See also

+

Module sqlite3

+
+

The sqlite3 module is included with Python 2.5+. Details on sqlite3 +connection and cursor objects can be found in the +Python sqlite3 documentation.

+
+
+
+
+

ENVI Spectral Libraries

+

While the EcostressDatabase provides a Python interface to the +ECOSTRESS Spectral Library, there may be times where you want to repeatedly access +a small, fixed subset of the spectra in the library and do not want to repeatedly +query the database. The ENVI file format enables storage of spectral libraries +(see ENVI Headers). SPy can read these files into a SPy +SpectralLibrary object.

+

To enable easy creation of custom spectral libraries, the EcostressDatabase +has a create_envi_spectral_library method that +generates a spectral library that can easily be saved to ENVI format. Spectra +in the ECOSTRESS library have varying numbers of samples over varying spectral +ranges. To generate the library we want to save to ENVI format, we need to specify +a band discretization to which we want all of the desired spectra resampled. +Let’s pick the bands from our sample hyperspectral image.

+
In [12]: bands = aviris.read_aviris_bands('92AV3C.spc')
+
+In [13]: print(bands.centers[0], bands.centers[-1])
+400.019989 2498.959961
+
+
+

We see from the output above that the bands range from about 400 - 2,500 nm +(we’re ignoring the fact that the bands at both ends have a finite width). +We would like to find library spectra that cover the entire spectral range for +our image, so we’ll check the band limits for the library spectra. But first, +let’s check the units of the spectra in the library.

+
In [14]: db.print_query('SELECT DISTINCT Measurement, XUnit, YUnit FROM Samples, Spectra ' +
+   ....:                      'WHERE Samples.SampleID = Spectra.SampleID AND ' +
+   ....:                      'Name LIKE "%stone%"')
+   ....: 
+Hemispherical reflectance|Wavelength (micrometers)|Reflectance (percent)
+Directional (10 Degree) Hemispherical Reflectance|Wavelength (micrometers)|Reflectance (percent)
+Reflectance|Wavelength (micrometers)|Reflectance (percent)
+Directional (10 degree) Hemispherical Reflectance|Wavelength (micrometers)|Reflectance (percent)
+Directional (10 degree) hemispherical reflectance|Wavelength (micrometers)|Reflectance (percent)
+Directional hemispherical reflectance|Wavelength (micrometers)|Reflectance (percent)
+
+
+

We see that all spectra are measures of reflectance but wavelengths are in units +of micrometers, whereas our sample image bands are in nanometers. To properly +query the spectra, we will need to specify the band limits in micrometers.

+
In [15]: db.print_query('SELECT COUNT() FROM Samples, Spectra ' +
+   ....:       'WHERE Samples.SampleID = Spectra.SampleID AND ' +
+   ....:       'Name LIKE "%stone%" AND ' +
+   ....:       'MinWavelength <= 0.4 AND MaxWavelength >= 2.5')
+   ....: 
+59
+
+
+

So it appears that 59 of the 78 “stone” spectra cover the desired band limits. +To create a new, resampled spectral library for these spectra, we call the +EcostressDatabase create_envi_spectral_library +method, passing it the list of spectrum IDs and our output band schema (in micrometers). +Since our bands are defined in nanometers, we will convert them before calling +the method.

+
In [16]: rows = db.query('SELECT SpectrumID FROM Samples, Spectra ' +
+   ....:         'WHERE Samples.SampleID = Spectra.SampleID AND ' +
+   ....:         'Name LIKE "%stone%" AND ' +
+   ....:         'MinWavelength <= 0.4 AND MaxWavelength >= 2.5')
+   ....: 
+
+In [17]: ids = [r[0] for r in rows]
+
+In [18]: print(ids)
+[14, 73, 167, 171, 203, 237, 275, 279, 384, 502, 532, 582, 601, 682, 744, 766, 788, 789, 1064, 1114, 1121, 1218, 1404, 1429, 1433, 1459, 1551, 1563, 1615, 1624, 1631, 1652, 1669, 1745, 1903, 1929, 2153, 2155, 2200, 2254, 2271, 2308, 2375, 2472, 2602, 2626, 2653, 2717, 2740, 2811, 2880, 3002, 3033, 3059, 3069, 3189, 3234, 3314, 3339]
+
+In [19]: bands.centers = [x / 1000. for x in bands.centers]
+
+In [20]: bands.bandwidths = [x / 1000. for x in bands.bandwidths]
+
+In [21]: lib = db.create_envi_spectral_library(ids, bands)
+
+
+

Now that we’ve created a resampled library of spectra, let’s plot the last +spectrum in the resampled library.

+
In [22]: f = plt.figure()
+
+In [23]: s = db.get_signature(ids[-1])
+
+In [24]: plt.plot(s.x, s.y, 'k-', label='original');
+
+In [25]: plt.plot(bands.centers, lib.spectra[-1], 'r-', label='resampled');
+
+In [26]: plt.grid(1)
+
+In [27]: plt.gca().legend(loc='upper left');
+
+In [28]: plt.xlim(0, 3);
+
+In [29]: plt.title('Resampled %s spectrum' % lib.names[-1]);
+
+
+_images/spectrum_resampled.png +

The resampled spectral library can be used with any image that uses the same +band calibration to which we resampled the spectra. We can also save the library +for future use. But before we save the library, we need to change the band units to the units +used in the band calibration (we need to convert from micrometers to nanometers).

+
In [30]: lib.bands.centers = [1000. * x for x in lib.bands.centers]
+
+In [31]: lib.bands.bandwidths = [1000. * x for x in lib.bands.bandwidths]
+
+In [32]: lib.bands.band_unit = 'nm'
+
+In [33]: lib.save('stones', 'Stone spectra from the ECOSTRESS library')
+
+
+

Saving the library with the name “stones” creates two files: “stones.sli” and +“stones.hdr”. The first file contains the resampled spectra and the second is the +header file that we use to open the library.

+
In [34]: mylib = envi.open('stones.hdr')
+
+In [35]: mylib
+Out[35]: <spectral.io.envi.SpectralLibrary at 0x7f61aab7f7b8>
+
+
+

References

+
+
Baldridge2009
+

Baldridge, A. M., Hook, S. J., Grove, C. I. and G. Rivera, 2008(9). +The ASTER Spectral Library Version 2.0. In press Remote Sensing of Environment

+
+
Meerdink2019
+

Meerdink, S. K., Hook, S. J., Roberts, D. A., & Abbott, E. A. (2019). +The ECOSTRESS spectral library version 1.0. Remote Sensing of Environment, 230(111196), 1–8.

+
+
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000..785bf6a Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 0000000..c9e66e1 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,123 @@ + + + + + + + Python Module Index — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + + +
+
+
+
+ + +

Python Module Index

+ +
+ s +
+ + + + + + + + + + + + + + + + + + + +
 
+ s
+ spectral +
    + spectral.io.aviris +
    + spectral.io.envi +
    + spectral.io.erdas +
    + spectral.io.spyfile +
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 0000000..453ec43 --- /dev/null +++ b/search.html @@ -0,0 +1,99 @@ + + + + + + + Search — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + + + + +
+
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..abc4734 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["algorithms","class_func_glossary","class_func_ref","fileio","graphics","index","installation","libraries","user_guide","user_guide_intro"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":1,"sphinx.domains.index":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,"sphinx.ext.todo":2,sphinx:56},filenames:["algorithms.rst","class_func_glossary.rst","class_func_ref.rst","fileio.rst","graphics.rst","index.rst","installation.rst","libraries.rst","user_guide.rst","user_guide_intro.rst"],objects:{"spectral.ColorScale":{__call__:[2,1,1,""],__init__:[2,1,1,""],set_background_color:[2,1,1,""],set_range:[2,1,1,""]},"spectral.SpyFile":{__getitem__:[2,1,1,""],__str__:[2,1,1,""],load:[2,1,1,""]},"spectral.algorithms.algorithms":{FisherLinearDiscriminant:[2,0,1,""],GaussianStats:[2,0,1,""],MNFResult:[2,0,1,""],PrincipalComponents:[2,0,1,""],TrainingClass:[2,0,1,""],TrainingClassSet:[2,0,1,""],bdist:[2,2,1,""],cov_avg:[2,2,1,""],covariance:[2,2,1,""],iterator:[2,2,1,""],mean_cov:[2,2,1,""],mnf:[2,2,1,""],msam:[2,2,1,""],ndvi:[2,2,1,""],noise_from_diffs:[2,2,1,""],orthogonalize:[2,2,1,""],ppi:[2,2,1,""],spectral_angles:[2,2,1,""]},"spectral.algorithms.algorithms.MNFResult":{denoise:[2,1,1,""],get_denoising_transform:[2,1,1,""],get_reduction_transform:[2,1,1,""],num_with_snr:[2,1,1,""],reduce:[2,1,1,""]},"spectral.algorithms.algorithms.PrincipalComponents":{denoise:[2,1,1,""],get_denoising_transform:[2,1,1,""],reduce:[2,1,1,""]},"spectral.algorithms.algorithms.TrainingClass":{__init__:[2,1,1,""],__iter__:[2,1,1,""],calc_stats:[2,1,1,""],size:[2,1,1,""],stats_valid:[2,1,1,""],transform:[2,1,1,""]},"spectral.algorithms.algorithms.TrainingClassSet":{__getitem__:[2,1,1,""],__iter__:[2,1,1,""],__len__:[2,1,1,""],add_class:[2,1,1,""],all_samples:[2,1,1,""],transform:[2,1,1,""]},"spectral.algorithms.classifiers":{GaussianClassifier:[2,0,1,""],MahalanobisDistanceClassifier:[2,0,1,""],PerceptronClassifier:[2,0,1,""]},"spectral.algorithms.classifiers.GaussianClassifier":{__init__:[2,1,1,""],classify_image:[2,1,1,""],classify_spectrum:[2,1,1,""],train:[2,1,1,""]},"spectral.algorithms.classifiers.MahalanobisDistanceClassifier":{__init__:[2,1,1,""],classify_image:[2,1,1,""],classify_spectrum:[2,1,1,""],train:[2,1,1,""]},"spectral.algorithms.classifiers.PerceptronClassifier":{classify:[2,1,1,""],classify_image:[2,1,1,""],classify_spectrum:[2,1,1,""],input:[2,1,1,""],train:[2,1,1,""]},"spectral.algorithms.detectors":{MatchedFilter:[2,0,1,""],ace:[2,2,1,""],matched_filter:[2,2,1,""],rx:[2,2,1,""]},"spectral.algorithms.detectors.MatchedFilter":{__call__:[2,1,1,""],__init__:[2,1,1,""],whiten:[2,1,1,""]},"spectral.algorithms.resampling":{BandResampler:[2,0,1,""]},"spectral.algorithms.resampling.BandResampler":{__call__:[2,1,1,""],__init__:[2,1,1,""]},"spectral.algorithms.spatial":{map_class_ids:[2,2,1,""],map_classes:[2,2,1,""]},"spectral.algorithms.transforms":{LinearTransform:[2,0,1,""]},"spectral.algorithms.transforms.LinearTransform":{__call__:[2,1,1,""],__init__:[2,1,1,""],chain:[2,1,1,""]},"spectral.config":{SpySettings:[2,0,1,""]},"spectral.database":{AsterDatabase:[2,0,1,""],EcostressDatabase:[2,0,1,""]},"spectral.database.AsterDatabase":{create:[2,1,1,""],create_envi_spectral_library:[2,1,1,""],get_signature:[2,1,1,""],get_spectrum:[2,1,1,""],print_query:[2,1,1,""],query:[2,1,1,""]},"spectral.database.EcostressDatabase":{create:[2,1,1,""]},"spectral.graphics.graphics":{get_rgb:[2,2,1,""]},"spectral.graphics.spypylab":{ImageView:[2,0,1,""],imshow:[2,2,1,""]},"spectral.graphics.spypylab.ImageView":{__init__:[2,1,1,""],class_alpha:[2,1,1,""],format_coord:[2,1,1,""],interpolation:[2,1,1,""],label_region:[2,1,1,""],open_zoom:[2,1,1,""],pan_to:[2,1,1,""],refresh:[2,1,1,""],set_classes:[2,1,1,""],set_data:[2,1,1,""],set_display_mode:[2,1,1,""],set_rgb_options:[2,1,1,""],set_source:[2,1,1,""],show:[2,1,1,""]},"spectral.image":{ImageArray:[2,0,1,""]},"spectral.io":{aviris:[2,3,0,"-"],envi:[2,3,0,"-"],erdas:[2,3,0,"-"],spyfile:[3,3,0,"-"]},"spectral.io.aviris":{open:[2,2,1,""],read_aviris_bands:[2,2,1,""]},"spectral.io.bipfile":{BipFile:[2,0,1,""]},"spectral.io.bipfile.BipFile":{open_memmap:[2,1,1,""],read_band:[2,1,1,""],read_bands:[2,1,1,""],read_pixel:[2,1,1,""],read_subimage:[2,1,1,""],read_subregion:[2,1,1,""]},"spectral.io.envi":{SpectralLibrary:[2,0,1,""],create_image:[2,2,1,""],open:[2,2,1,""],save_classification:[2,2,1,""],save_image:[2,2,1,""]},"spectral.io.envi.SpectralLibrary":{save:[2,1,1,""]},"spectral.io.erdas":{open:[2,2,1,""]},"spectral.io.spyfile":{SubImage:[2,0,1,""],transform_image:[2,2,1,""]},"spectral.io.spyfile.SubImage":{read_band:[2,1,1,""],read_bands:[2,1,1,""],read_pixel:[2,1,1,""],read_subimage:[2,1,1,""],read_subregion:[2,1,1,""]},"spectral.spectral":{open_image:[2,2,1,""]},spectral:{BandInfo:[2,0,1,""],ColorScale:[2,0,1,""],SpyFile:[2,0,1,""],calc_stats:[2,2,1,""],create_training_classes:[2,2,1,""],kmeans:[2,2,1,""],linear_discriminant:[2,2,1,""],principal_components:[2,2,1,""],save_rgb:[2,2,1,""],view:[2,2,1,""],view_cube:[2,2,1,""],view_indexed:[2,2,1,""],view_nd:[2,2,1,""]}},objnames:{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","function","Python function"],"3":["py","module","Python module"]},objtypes:{"0":"py:class","1":"py:method","2":"py:function","3":"py:module"},terms:{"0x7f61aab7dcc0":7,"0x7f61aab7f7b8":7,"101st":3,"10th":2,"12f":7,"145x145":9,"14x":5,"30th":[0,2],"51st":[2,3],"6th":3,"92av3c":[0,2,3,4,7,9],"92av3gt":[0,2,4,9],"99th":0,"abstract":2,"byte":[2,3],"case":[0,2,4],"catch":0,"class":[0,3,5,7,8],"default":[0,2,4,5],"final":[0,4],"float":[0,2,3,7],"function":[3,4,5,6,8,9],"import":[0,2,3,4,7,9],"int":[2,3],"long":2,"new":[0,1,2,3,4,7],"return":[0,1,2,3,4,7],"short":4,"super":4,"switch":4,"true":[0,2],"try":2,"while":[0,1,2,4,6,7,9],ACE:0,AND:[2,7],And:[0,5,6],But:7,Exelis:[2,3],For:[0,2,4,5],GIS:[0,2,4,9],IDs:[0,2,4,7],One:2,That:0,The:[0,2,4,5,6,7,8,9],Then:4,There:7,These:[0,2],Use:2,Using:4,With:[0,4],__call__:2,__class__:3,__getitem__:2,__init__:2,__iter__:2,__len__:2,__str__:[2,4],a_1:2,a_2:2,abbott:7,abil:[4,5,7],abl:4,about:[0,2,3,5,7],abov:[0,2,6,7],absenc:0,absolut:6,accept:[0,2,4,5],access:[0,2,3,4,5,7],accord:[1,2],accordingli:2,accur:2,accuraci:[0,2],ace:[0,1,2,5],achiev:2,acoust:[0,2],acquir:7,activ:2,actual:[2,3],adapt:[0,1,4,5],add:[2,5],add_class:2,added:2,addit:[0,2,5],adenostoma:7,adjac:[0,2,4],adjust:[0,2,4,5],affect:[2,5],after:[0,2,4],again:[0,2,4,6],against:[0,4],aggreg:2,airborn:3,algorithm:[1,2,3,5,8],alias:2,all:[0,2,3,4,5,6,7],all_sampl:2,alloc:3,allow:[0,2,4,5],allow_nan:2,allow_unmap:2,almost:[0,4],alo:7,along:[0,2,4,5],alpha:[2,4],alreadi:[2,4],also:[0,2,3,4,5,7],alter:[2,4],altern:[2,3],alwai:[0,2,3,5],amount:[3,4],analysi:[0,2,4,9],analyst:4,analyt:4,analyz:[2,3],andadjust:2,angl:[1,2],ani:[0,2,4,6,7],ann:1,anna:2,anomal:0,anomali:[1,5],anoth:2,anyon:2,appar:0,appear:[0,2,4,7],append:2,appli:[0,1,2,4,5],applic:[2,4,5],approach:0,appropri:[0,2,5],approxim:3,apr:8,arbitrari:[2,5],arbitrarili:0,arborescen:7,arg:2,argillac:7,argmax:2,argment:2,argmin:[0,2],argument:[0,2,4,5,7],arkos:7,around:[0,2,4,5],arr:3,arrai:[0,1,2,3,4],artifici:1,arvelyna:2,ascii:7,asd:7,aspect:4,asphalt:3,assess:4,assign:[0,2,4],associ:[0,2,3,4,5,7,9],associatd:2,associet:2,assum:[0,2,9],assumpt:0,aster2:2,aster:[2,5,7],aster_data_dir:2,aster_lib:2,asterdatabas:7,atsushi:2,attempt:[0,2,6],attribut:[2,4,7],august:2,auto_scal:2,automat:[0,2,3,4,5],avail:[0,2,5,6],averag:[0,1,2,5],aviri:[0,1,4,7,9],avoid:[3,6],axes:[0,2,4],axi:[2,4,5],b_1:2,b_2:2,back:[2,4],backend:[2,4,5],background:[0,2,5],backpropag:2,bad:5,baldridg:7,baldridge2009:7,band6:3,band:[1,2,3,4,5,7,9],band_fil:2,band_quant:2,band_unit:[2,7],bandinfo1:2,bandinfo2:2,bandinfo:[1,3],bandresampl:[0,1],bands2:0,bandwidth:[2,7],bandwidth_stdev:2,bar:2,bare:3,base:[0,2,3,6],basi:2,basic:2,batch:2,bdist:[1,2],bdistanc:2,becaus:[0,3,4,6],becom:0,been:[2,3,4,5,7,9],befor:[0,2,3,4,7],begin:[0,2,4],behav:3,behavior:4,behaviour:4,being:[0,2,4,5],bele:7,belong:2,below:[2,4],berlin:[0,2],berman:2,best:[4,7],better:0,betula:7,between:[0,1,2,4,5,7],beyond:4,bhattacharyya:1,bia:2,big:2,bil:[2,3],bilfil:[2,3],binari:6,bind:4,bip:[2,5],bipfil:[2,3],bit:[2,3,4],black:[0,2],blank:[2,4,5],blend:2,blob:7,block:[4,5,6],blue:2,bmp:2,boardman:2,bool:2,border:2,both:[2,4,7],bound:[0,2,4],box:[0,2,4],branch:7,breccia:7,breviti:9,brick:3,brighter:0,brown:7,bsq:[2,5],bsqfile:[2,3],buffer:[2,4],bug:[5,6],build:5,burdensom:0,button:4,bxb:2,bxc:2,byteord:2,c_1:2,c_2:2,c_b:0,c_w:0,cach:[2,3,5],caco3:7,cal:3,cal_filenam:2,calc_stat:1,calcul:[0,1,2],calibr:[1,2,3,7,9],california:2,call:[0,2,3,4,5,6,7],callabl:[1,2],callback:2,calul:2,can:[0,1,2,3,4,5,6,7],cancel:4,cannot:2,canon:0,canva:4,canvas:2,capabl:6,capit:2,captur:0,care:2,cast:2,categor:5,categori:9,caus:[2,5],cdrom:2,center:[0,2,7],centers1:2,centers2:2,centers_stdev:2,centroid:0,certainli:4,cfar:[0,2],chain:2,chang:[2,4,5,7],channel:2,character:2,check:[2,6,7],chi2:0,chip:9,choos:[0,4],christian:5,citer:0,class1:2,class2:2,class_alpha:[2,4],class_color:2,class_id:2,class_id_map:2,class_imag:2,class_map:2,class_mask:2,class_nam:2,class_prob:2,classif:[2,4,5,8],classifi:[0,1,2,5],classify_imag:[0,2],classify_spectrum:2,classindex:2,classmask:2,classmethod:2,classprob:2,click:[2,4,5],clip:2,clmap:[0,2],clmap_train:0,clone:6,close:4,cluster:[1,2],cmap:2,coars:7,code:[0,5,8],coher:[0,1,5],coincid:2,col:[0,2,4],col_bound:2,col_rang:2,col_start:2,col_stop:2,collect:[0,9],colon:6,color:[0,1,2,4,5],color_scal:2,colorscal:1,column:[0,2,3,4,5,7],columnss:2,com:[5,6],combin:[2,4],command:[0,2,4,5,6,7,9],comment:5,commerci:[2,3],common:[0,1,2,4,5],commonli:0,compar:[0,2],comparison:2,compen:0,complet:[2,4],complex:0,compon:[1,2,4,5],compress:4,compromis:0,comput:[0,1,2,5,9],computation:0,concret:3,configur:[1,8],confirm:4,conflict:2,conjunct:2,connect:7,consid:[0,2,3,4],consist:5,consol:[2,6],constant:[0,2],constrain:2,construct:3,constructor:2,consum:[2,3],contain:[0,1,2,3,4,7,9],content:9,context:4,contigu:[0,2,4],continu:[2,5],continuum:2,contrast:[0,5],contribut:[0,2,5],control:[1,4,5],conveni:[0,2,4],converg:[0,2],convert:[2,7],cool:4,coordin:2,copi:[2,7],corner:[2,4],correct:[2,5],correl:[0,4],correspond:[0,1,2,3],cosin:[0,1,5],could:[0,2],count:[2,4,7],counterclockwis:2,cov:[0,2],cov_avg:[0,1,5],cov_b:2,cov_w:2,covari:[0,1,4,5],cover:[0,2,4,7],creat:[0,1,2,3,4,5,7],create_envi_spectral_librari:[2,7],create_imag:[1,3,5],create_training_class:[0,1],createtrainingclass:2,creation:7,criterion:0,ctrl:[0,2,4],cube:[1,2,4,5],cumul:[0,2,5,9],cup95eff:[2,3],cur:2,current:[0,2,4,5,6,7],curs:0,cursor:[2,4,7],custom:[2,4,7],cview:2,cxb:[0,2],cycl:[2,4],cyclosil:7,dark:7,darker:0,dat:2,data:[1,2,5,6,7,8],data_dir:2,databas:[1,2,7],date:[0,3,8],dblclick:4,debat:4,decid:4,declar:0,decreas:4,defaultcolorscal:2,defin:[0,2,4,6,7],degre:7,delimit:6,demand:[2,3],denois:2,densiti:4,depend:[2,4,8,9],deprec:[5,6],depth:[2,4],deriv:2,descend:[0,2],descript:[0,1,2,3,7,9],design:3,desir:[0,2,4,5,7],dest_class_imag:2,destin:[0,2],detail:[0,2,5,7],detect:[0,2,5,8],detector:[1,5,8],determin:[2,4],dev:2,develop:4,diagon:[0,2],dict:2,dictionari:2,differ:[0,1,2,4,5,6],differenec:2,difficult:4,digit:[0,2,4],dim_in:2,dim_out:2,dimens:[2,4],dimensin:4,dimension:[2,5,8],direct:[0,2,7],directli:[2,4,7],directori:[2,3,6],disabl:[2,5],discret:[0,1,2,7],discreti:2,discrimin:[1,2],disk:[2,3],displai:[0,2,3,5,6,8],distanc:[0,1],distinct:7,distinguish:4,distribut:[0,2,5,8],distutil:5,divid:[0,2],document:[4,7,8],doe:[0,2],doesn:4,dolomit:7,domain:3,don:[5,7],done:[0,3,4],dot:2,doubl:[2,4,5],down:4,download:[5,6,9],downsid:3,drag:4,drift:0,dry:2,dtype:2,due:[0,4],duplic:2,dure:2,dynam:[0,2,4,5],each:[0,2,3,4,5],easi:[4,7],easili:[0,4,7],easy_instal:6,echo:4,eco_data_ver1:[2,7],ecostress:[1,2,5,8],ecostressdatabas:[1,7],edit:5,effect:2,effici:[2,3,5],eig:2,eigenvalu:[0,2],eigenvector:[0,2],eight:4,either:[0,2,4,5,6,7],electr:9,element:[0,2],elimin:2,embed:2,empti:[1,2],emul:2,enabl:[2,4,5,7],end:[0,2,7],endian:2,endmemb:2,eng:9,enhanc:[2,5],enough:0,ensur:2,enter:4,entir:[0,2,4,7,8,9],enumer:0,envi:[1,5,8],envi_support_nonlowercase_param:2,envidatafilenotfounderror:2,environ:[2,3,6,7],equal:[0,2],equival:2,erda:[1,9],erron:4,error:[0,2],esim:5,estim:[0,1,5],estimag:2,euclidean:2,evalu:[0,2],evaul:2,even:[2,3],evenli:2,event:2,everi:2,examin:4,exampl:[0,2,4,5,7,9],except:[0,2,5,6],exclud:2,exclus:0,execut:[0,1,2],exist:[2,3],exit:4,expect:[0,2],expens:0,explicit:[0,2],explicitli:[2,3,4,6],explor:4,express:2,ext:2,extens:[1,2,3],extern:[5,7],extract:2,extrem:[2,4,7],f970619t01p02_r02:[0,3],f970619t01p02_r02_sc01:3,f970619t01p02_r02_sc04:0,f_1:2,f_2:2,f_3:2,face:[3,5],facilit:4,fact:7,factor:[2,4],fals:2,fasciculatum:7,faster:[0,2,5],featur:[0,2,5,6,8],few:[0,4,5],fewer:0,field:2,fignum:2,figsiz:2,figur:[0,2,4,7],file:[5,6,7,8],file_basenam:2,filenam:2,fill:4,filter:[1,2,5],find:[0,2,6,7],fine:7,finer:5,finit:7,first:[0,2,3,4,7],fisher:[1,2],fisherlineardiscrimin:1,five:0,fix:[0,2,5,6,7],flag:2,fld:[0,2],fld_error:0,float32:[2,3],focu:[2,4],follow:[0,2,3,4,6,7,9],forc:[0,2],form:[2,4],format:[1,5,7,8,9],format_coord:2,formula:0,found:[2,6,7],frac:[0,2],fraction:[0,1,5],frame:2,free:5,freeli:6,frequent:2,from:[0,1,2,3,4,5,7,8,9],full:[0,2,3,5],futur:[5,7],fwhm1:2,fwhm2:2,fwhm:[0,2],gaussian:[1,2],gaussianclassifi:[0,1],gca:7,gener:[0,1,2,3,5,7,9],geometri:4,geoscienc:2,geospati:[2,3],get:[0,2,4,7],get_denoising_transform:2,get_reduction_transform:2,get_rgb:[1,4],get_signatur:[2,7],get_spectrum:[2,7],git:8,github:[5,6],give:2,given:[0,1,2,4],global:[0,5],glossari:[5,8],glrt:2,glut:4,gmlc:[0,1,5],goethite_1:7,good:[0,9],gov:3,grai:[2,7],gram:[0,1,2],graphic:[6,8],grass:2,greater:[0,2],greatest:2,green:2,grei:0,greyscal:[2,4],greywack:7,grid:[0,7],ground:[0,1,2,4,5,9],group:0,grove:7,gterror:0,gtresult:0,gui:[4,5,6],guid:[5,9],had:[0,5],half:[0,2],handl:[2,5],handler:2,happen:0,has:[0,1,2,3,4,5,6,7,9],have:[0,2,3,4,5,6,7],hdl:2,hdr:[2,3,7],hdr_file:2,header:[1,2,5,7],height:2,help:4,hemispher:7,here:[2,5,7],high:[0,2,4,5,7],higher:[0,2,4],highest:2,highlight:4,hist:0,histogram:[0,2,4,5],hold:[2,4],home:3,homogen:[1,5],hook:7,host:5,how:[0,2,5],howev:[2,3,4],hsi:[5,6,8],http:[2,3,5,6],hundr:[0,4],hydroxid:7,hymap:2,hypercub:[2,5,8],hyperspectr:[0,1,2,3,4,5,6,7,9],idea:9,ideal:4,ident:2,identifi:[2,3,4],ids:7,ieee:[0,2],ignor:[0,2,7],imag:[0,1,2,5,6,7,8,9],imagearrai:3,imageri:[2,3,4,5],imageview:[0,1,4],img1:0,img:[0,2,3,4],img_fld:0,img_pc:0,implement:[0,2,7],improv:[0,2,3,5],imshow:[0,1,4,5],imshow_background_color:2,imshow_class_alpha:2,imshow_disable_mpl_callback:2,imshow_enable_rectangle_selector:2,imshow_figure_s:2,imshow_float_cmap:2,imshow_interpol:2,imshow_kwarg:2,imshow_stretch:2,imshow_stretch_al:2,imshow_zoom_figure_width:2,imshow_zoom_pixel_width:2,inc:2,inch:2,includ:[2,4,5,7],incorrect:5,increas:[2,4],increment:2,indefinit:5,independ:[2,4],index:[0,1,4,5,6],indiana:9,indic:[0,1,2,3,4],individu:2,infint:5,info:[3,5],inform:[0,3,4],infrar:[0,2,3],inherit:2,initi:[0,2,4,5],inner:[0,2,5],inosil:7,input:[0,2,4,5,8],inspect:0,instal:[5,8],instanc:[2,3],instanti:[2,7],instead:[0,2,7],instrument:7,int16:3,integ:[0,2,4,7],integr:[0,2,5],intend:[2,4],intens:2,interact:[0,1,2,5,6],interest:[0,4],interfac:[2,5,7,8],interleav:[2,3,5],intermedi:2,interpol:[0,2,4],interpret:[2,4,5],interrup:2,interrupt:0,introduc:[0,5,9],introduct:[0,2,5,8],inv:2,inv_cov:2,invalid:2,invalidfileerror:2,invari:2,invers:[0,2],invert:[0,2],ioerror:2,ipython:[2,5,6,8],isn:7,issu:[0,4,5,7,9],item:2,iter:[0,1,3],its:[0,2,4,6,7],jame:2,jan:2,jhu:7,jia:[0,2],jpeg:[1,2],jpg:[2,4],jpl077:7,jpl:[3,7],jump:5,just:[0,6],kawakami:2,kei:[2,4,7],keybind:4,keyboard:[0,2,4,5],keyboardinterrupt:[0,2],keypress:2,keyword:[0,2,4],kmean:[0,1,5],know:[0,2],knowledg:0,kraut:2,kruse:2,kwarg:2,label:[0,1,2,5,7],label_region:2,lafayett:9,lambda:[0,2],lan:[0,1,4,9],land:9,landgreb:9,landgrebe1998:9,larg:[0,2,3,4,6,7],larger:[0,2],last:[2,7],lastli:7,latest:6,latter:4,layer:[0,1,2],lclick:2,learn:2,leas:2,least:0,lee:2,left:[0,2,4,7],legend:7,len:[0,2],length:[2,3],lenta:7,less:[0,2,6],let:[0,3,7],level:[0,2,5],lib:[3,7],librari:[1,2,3,5,6,8],licens:5,light:4,like:[0,2,3,4,6,7,9],likelihood:[1,2],limeston:7,limit:[0,2,4,5,7],line2d:7,line:[2,3,4,5,7],linear:[1,2,4,5],linear_discrimin:[0,1],linearli:2,lineartransform:[1,5],list:[0,2,5,6,7],littl:2,load:[0,2,4,8],lobata:7,loc:7,local:[0,2,5,6],locat:[2,3,4,5],log:[0,2,6],log_det_cov:2,longer:2,look:[0,2,4],lower:[0,2,4],lowerleft:2,lowerright:2,lying:4,made:2,magnifi:4,magnitu:0,mahalanobi:[0,1,2,5],mahalanobisdistanceclassifi:[0,1],mai:[0,2,3,4,5,6,7],main:[2,4,5],make:[0,4,6,7],manag:2,manhattan:2,mani:[0,2,4,5,9],manipul:[2,5],map:[0,1,2,5,9],map_class:[1,5],map_class_id:[1,5],mapper:[0,1],march:5,mark:2,mask:[0,2,4],match:[1,2,5],matched_filt:[0,1,5],matchedfilt:5,materi:[0,7],math:8,matplotlib:[0,1,2,4,5,6,7],matplotlibrc:4,matric:0,matrix:[0,2],max:[0,2],max_iter:2,maxim:0,maximum:[1,2],maxiter:2,maxwavelength:7,mean:[1,2,3,4,5],measur:[0,2,7],measurement_id:2,meerdink2019:7,meerdink:7,member:[2,3,4],memmap:[2,5,8],memmep:2,memori:[0,2,3],menu:4,messag:[2,4,5],met:6,metadata:[2,3,4,7],method:[0,2,3,4,5,7],metric:0,mfscore:0,micromet:7,middl:2,mielk:5,might:6,migrat:0,min:2,min_sampl:2,miner:7,mineralog:2,minimum:[0,1,5],minwavelength:[2,7],mirror:[4,5],miscellan:8,mit:5,mitig:0,miyatak:2,mlp:[1,2],mnf:[1,5],mnfr:2,mnfresult:[1,2],mode:[2,5,6],modif:2,modifi:[0,1,2,4,5],modifii:4,modul:[2,4,5,6,7,9],momentum:2,momos:2,more:[0,2,3,4,5],most:[0,2,4,6,7],mous:[2,4,5],move:5,msam:[0,1,5],mu_b:[0,2],mu_t:[0,2],much:[0,2,3],multi:[0,1,2],multilpl:2,multipl:[0,1,2,3,4],multispectr:[4,9],must:[0,2,4],mutipl:5,mxn:[0,2,3],mxnx3:2,mxnxb:[2,3],mxnxc:[0,2,3],mxnxl:2,mylib:7,name:[2,3,4,5,6,7,9],namespac:[5,9],namibia:2,nan:[0,2],nanomet:[2,7],nanvalueerror:2,napc:2,narrow:4,nasa:3,nativ:2,natur:2,nband:[0,2],nclass:2,ncluster:2,ncol:2,ndarrai:[2,3,4,5],ndarrrai:2,ndvi:1,ndwindow:5,ndwindowproxi:2,nearest:[0,2,4],necessari:[2,3,4],need:[0,2,3,6,7],neg:[0,2,4],neighborhood:0,neither:2,ness:2,net:[2,5],network:[1,2],neural:[1,2],neuron:2,nevertheless:4,new_imag:2,new_image2:2,newer:6,newli:4,next:[0,2,7],nfeatur:2,nguno:2,nicolet:7,nir:[0,2],niter:2,nois:[1,5],noise_from_diff:[1,5],non:[2,4,5,6],none:[2,7],nonphotosyntheticveget:7,nonzero:2,nor:2,normal:[0,1,2],note:[0,2,3,4,5,6],notebook:[2,5],noteworthi:2,noth:2,notic:[0,4],now:[0,4,5,7],nrow:2,nsampl:2,num:2,num_tic:2,num_with_snr:2,numband:3,number:[0,2,3,4,5,6,7],numcol:3,numer:[2,4,5,7],numpi:[0,2,5,6,8],numrow:3,numvalu:7,object:[0,1,2,3,4,5,7],observ:2,obtain:0,obviou:[0,4],occur:2,oct:[0,2],octant:[2,4],odd:2,offset:2,often:[0,2,4,6],ohspit:5,old:5,older:7,omit:2,on_iter:2,onc:[0,2,7],one:[0,2,3,4,6,7],onli:[0,2,3,4],onto:[0,2,4],open:[0,1,3,4,5,6,7],open_imag:[0,1,3,4,5],open_memmap:[2,3,5],open_zoom:2,opengl:2,oper:[2,3,4,5],opert:4,optic:[0,2],option:[0,1,2,3,4,5,6],order:[0,2],origin:[0,2,4,7],orthogon:[0,1],orthonorm:2,oshigami:[2,5],other:[0,2,3,4,6],otherwis:2,our:[0,3,4,7],out:[0,3,4,6,7],outer:[0,2,5],outlier:0,output:[2,7,8],over:[0,1,2,5,7,9],overal:2,overlai:[1,2,4,5],overlaid:2,overlap:[0,2],overrid:2,overridden:5,overwritten:2,owner:7,packag:[2,3,5,8],page:6,pair:2,palett:[2,4],pan:[2,4,5],pan_to:2,param:2,paramet:[0,2,4,5],pars:2,part:0,partial:[2,4],particles:7,particular:[0,2,3,4],particularli:[2,4,5],pasadena:2,pass:[0,2,4,7],path:[2,6],pattern:[0,2],pc_0999:0,pca:1,pcdata:2,pcimag:2,penalti:3,percent:[2,7],percentag:0,percentil:[0,5],perceptron:[0,1,2],perceptronclassifi:[0,1,5],perfect:2,perform:[0,1,2,3,4,5],perkin:7,perpendicular:4,perspect:9,phase:7,phyllosil:7,pick:7,pil:[2,6],pillow:6,pip:[5,8],pixel:[0,1,3,4,5,9],placehold:2,plane:4,plot:[0,2,5,6,7,8],plt:[0,7],plu:2,png:[1,2],point:[0,2,3,4],pollut:0,popular:[2,3],portion:[3,4],posit:[0,2,4],possibl:[4,5],post:2,postiv:2,potenti:[0,5,7],ppf:0,ppi:[1,5],pre:2,precedur:7,prefac:6,prefer:[2,3],presenc:0,present:[2,9],preserv:2,press:[2,4,7],prettier:4,prevent:[0,2],previou:[0,2,9],previous:[2,3],primari:[1,2,5,7],primarili:[0,3,5],princip:[1,2,4,5],principal_compon:[0,1,4],principalcompon:[0,1],print:[0,2,3,4,7],print_queri:[2,7],prior:[0,2,4,6],probabl:[0,2,7],problem:[0,2],proced:2,process:[0,2,3,5,6,7],produc:[0,1,2,4],programat:4,progress:[2,5],project:[0,2,4,6],prompt:[4,5],properli:7,properti:2,provid:[0,1,2,3,4,5,6,7],proxi:4,purdu:9,pure:5,puriti:[1,5],purpos:0,pylab:[4,5,6,7],pyopengl:6,pypi:[5,6],pyplot:[0,2],python:[3,4,6,7],qt4agg:[4,5],quadrant:4,quantiz:3,quercu:7,queri:[1,2,7],quickli:4,quit:[3,6],rais:[2,5],random:[2,4],randomli:4,randomz:4,rang:[0,2,7],rank:[0,2],rapidli:4,raster:[1,2,5,6,8],rate:[2,4],rather:[0,2,4],ratio:[0,2],ravel:0,raw:0,rcdefault:2,reach:[0,2],read:[1,2,4,5,7,8],read_aviris_band:[0,1,2,3,4,7],read_band:[0,2,3,4],read_pixel:[2,3],read_subimag:[2,3],read_subregion:[2,3],reassign:[0,2,4],recalcul:2,recent:[2,6],recogn:[2,3],recommend:[2,7],recomput:[0,2],rectangl:[2,4],rectangular:[2,3,4],recurs:5,red:[0,2,3,7],redirect:[2,5],reduc:[0,1,2,4],reduct:[2,4,8],reed:[0,2],reed_yu_1990:0,refer:[0,2,3,7,9],referenc:2,refin:2,reflect:[0,2,7],refresh:[2,4],regardless:3,region:[0,1,2,3,4,5],regist:[2,3],rel:0,relat:[2,4,5,7],relationship:7,releas:[4,5,6,8],remain:[0,4],rememb:2,remot:[0,2,7],remov:[0,2],renam:5,render:[2,4,6],repeat:0,repeatedli:7,replac:2,repositori:[5,8],repres:[0,2,4],represent:4,request:[3,4,7],requir:[0,2,3,6],resampl:[1,2,7],reset:4,resid:6,residu:2,resolut:[0,2,7],resolv:5,respect:[0,2,4],respons:[0,2],result:[0,2,3,4,5,7],result_map:2,resum:0,retain:[0,2],retrain:2,retriev:7,reus:2,review:4,rfl:[0,3],rgb:[0,1,2,3,8],richard:[0,2],richards1999:0,right:[0,2,4],rivera:7,robert:7,root:[2,6],rotat:[0,4,5],round:2,row:[0,2,3,4,5,7],row_bound:2,row_rang:2,row_start:2,row_stop:2,run:[0,2,3,4],rxval:0,sam:[1,2,5],same:[0,2,3,4,7],sampl:[0,2,3,4,5,7],sample_id:2,sample_nam:[2,7],sampleid:[2,7],samplenum:7,samples_per_class:2,sandi:7,sandston:7,save:[1,2,3,5,6,7,8],save_classif:[1,5],save_imag:[1,3,5],save_rgb:[1,4,5],scalar:2,scale:[0,1,2,4],scene:2,scharf:2,schema:7,schmidt:[0,1,2],school:9,scipi:0,score:[0,2,5],screen:4,script:5,search:2,second:[0,4,7],section:5,see:[0,2,4,5,6,7],select:[0,2,4,7],selector:2,semi:[2,4],sen:2,sens:[0,2,7],sensor:0,sensorcalibrationid:7,sep:2,separ:[2,3,4,7],seper:2,sequenc:2,sequenti:2,set:[0,1,2,4,5,7],set_background_color:2,set_class:2,set_data:2,set_display_mod:[2,4],set_featur:[2,4],set_rang:2,set_rgb_opt:[2,4],set_sourc:2,setup:6,setuptool:6,sever:[2,4,5,6],shape:[0,2,3],share:2,shift:[2,4],shoko:2,should:[0,2,4,5,6,9],show:[0,2,4],show_progress:2,shown:2,shrink:2,shrub:7,shuichi:2,side:[2,4],sig:2,sigma:[0,2],sigma_b:0,sigmoid:2,signal:[0,2,9],signatur:2,signific:[3,5],significanli:0,significantli:[0,5],silic:7,similar:[0,2,4],similarli:[2,3,4],simpl:[0,2,5,7],simpli:[0,2,6],simplifi:5,simultan:4,sinc:[0,2,3,4,7],singl:[0,2,3,4],singular:0,site:5,situat:[0,2],size:[0,2,4],sli:[2,7],slice:2,slow:2,slower:3,small:[0,2,3,7,9],smaller:[0,4],smallest:[0,2],smooth:3,snip:[0,7],snr:2,softwar:[2,3,5],solut:[2,3],solv:[0,2],some:[0,1,2,4,5],someth:4,somewhat:3,somewher:2,soon:5,sort:[0,2],sourc:[0,2,3,4,5,8],sourceforg:5,southern:2,space:[0,1,2,4],span:[2,4],spatial:4,spc:[0,3,4,7,9],specfi:2,specif:[3,4,6],specifi:[0,2,3,4,5,7],spectal:5,spectra:[0,2,3,4,7],spectral:[3,4,6,9],spectral_angl:[0,1],spectral_data:[2,3,6],spectrallibrari:[1,7],spectralpython:[5,6],spectromet:3,spectrum:[0,2,5,7,8],spectrumid:[2,7],speech:[0,2],spell:2,springer:[0,2],spy:[0,1,2,3,4,7,9],spy_color:[2,4],spyfil:[4,5,8],spyimag:2,spymath:2,spyset:[4,6],sql:2,sqlite3:[2,7],sqlite:[2,7],sqlite_filenam:2,sqrt_cov:2,sqrt_inv_cov:2,squar:[0,2],src_class_imag:2,stabl:[2,6],stack:2,standard:[3,4,5],start:[0,2,3,5,6,8],start_clust:[0,2],stat:[0,2],statement:2,statist:[0,2,5,8],statistc:2,stats_valid:2,statu:2,std:2,stdout:2,step:4,stephen:2,still:[0,2,3,4,5],stochast:2,stone:7,stonei:7,stop:[0,2],storag:[3,7],store:[2,6,7],str:2,straight:5,stretch:[0,2,4,5],stretch_al:[0,2,4],string:2,strong:[0,4],strongli:0,studi:9,style:9,sub:[2,3,4,5],subclass:[3,7],subscript:[2,3],subsequ:[2,4],subset:[2,7],subspac:2,subtract:2,success:9,sudo:6,suffer:[0,4],suffici:[0,4],suitabl:1,summari:2,superced:[2,7],supervis:8,support:[2,4,5,7,8],suppos:7,suppress:2,supress:4,sure:6,swap:5,sys:2,system:[4,5,6],tabl:[0,2,7],tail:2,take:[0,2,7],taken:2,target:[2,5,8],taro:2,tatsumi:2,term:2,termin:2,test:[5,9],text:[2,7],than:[0,2,4,5,6],thank:5,thei:[0,2,4,6,7],them:[0,7],thenumb:3,theori:9,therefor:[0,2,3,4],thes:2,thi:[0,2,3,4,5,7,9],third:[2,4],thoma:[2,3],those:0,thousand:2,three:[2,4,7],threshold:[0,2],through:[2,4,7],tiff:2,time:[0,2,3,4,5,7],tir:7,titl:[2,7],tkagg:4,togeth:2,toggl:4,too:[0,3],tool:[2,4],top:[0,2,4,5],total:[0,2],trademark:[2,3],train:8,training_data:2,training_error:0,trainingclassset:[0,1,2],trainingdata:2,tran:[0,2],trane:2,transact:2,transform:[0,2,4,5,8],transform_imag:1,transformedimag:2,translat:4,transpar:[2,4,5],treat:0,tree:7,triplet:2,truth:[0,1,2,4,5,9],tupl:[2,7],tutori:9,two:[0,1,2,4,7],txt:[5,7],type:[2,4,6,7],typeerror:2,typic:[2,4],typical:2,ucsb:7,uezato:2,uint16:2,unabl:4,unassign:4,unbias:2,unclassifi:2,uncorrel:0,under:5,underli:2,unit:[2,5,7],univ:9,unix:6,unknown:[0,2],unlabel:[0,2,4],unless:[2,6],unlik:[2,3],unmap:2,unmix:2,unmodifi:2,unpack:6,unsign:2,unsupervis:[1,2,4,5,8],until:[0,2,3,4],unusu:[2,3],updat:[2,4,5],upper:[0,2,4,7],uri:2,url:5,usa:2,usag:2,use:[0,1,2,3,4,5,6,7,9],use_memmap:[2,5],used:[0,1,2,3,4,5,6,7],useful:[2,4,5],user:[2,5,9],uses:[0,3,4,5,7],using:[0,1,2,3,4,5,6,7],using_memmap:2,usual:2,util:[4,5,8],val:2,valid:2,valu:[0,1,2,3,4],valueerror:2,vari:7,variabl:[2,3,6],varianc:[0,2],variat:0,variou:[0,2,4,5,9],vec:2,vector:[1,2],veget:[0,1,2,7],veri:[0,3],verion:2,version:[2,4,5,6,7],vh282:7,vh334:7,via:[1,2,3,5],vice:0,view:[0,1,4,5],view_cub:[1,4,5,6],view_nd:[1,4,5,6],viewabl:2,viewint:4,visibl:[0,3,4],visual:[3,4,5,8],vol:[0,2],vswir:7,wai:[3,4,7],want:[3,4,5,6,7],warn:5,wavelength:[2,4,7],weather:3,web:5,weight:[0,2],well:[0,2,3,4,9],were:[0,2,3,4],west:9,what:4,whatev:4,when:[0,2,3,4,5],whenev:6,where:[0,2,3,4,6,7],wherea:7,whether:[2,3],which:[0,2,3,4,5,6,7,9],whichev:4,white:[0,4],whiten:2,whiter:0,who:2,whose:[0,2],width:[0,2,7],win:2,window:[0,2,4,5,6],within:[0,2,3,4],without:[0,2,4],woodyatt:2,word:3,work:[3,6],wors:3,would:[0,3,4,6,7,9],wrapper:[2,4,5],writabl:2,write:[2,3],written:[0,2],wrong:5,www:5,wx_gl_depth_siz:[2,4],wxagg:4,wxpython:6,wxwidget:5,xdata:[2,4,7],xlim:7,xunit:7,yajima:2,yamaguchi:2,yasushi:2,ydata:7,yessi:2,yield:[0,2],you:[0,2,3,4,5,6,7,9],your:[0,4,6],yourself:0,yunit:7,yuu:2,zero:[0,2,4],zone:0,zoom:[2,4,5]},titles:["Spectral Algorithms","Class/Function Glossary","Class/Function Documentation","Reading HSI Data Files","Displaying Data","Welcome to Spectral Python (SPy)","Installing SPy","Spectral Libraries","Spectral Python (SPy) User Guide","Introduction"],titleterms:{"class":[1,2,4],"function":[0,1,2],"new":5,ACE:2,The:3,adapt:2,addit:4,algorithm:0,angl:0,anomali:[0,2],asterdatabas:2,aviri:[2,3],band:0,bandinfo:2,bandresampl:2,bhattacharyya:2,calc_stat:2,capabl:4,classif:[0,1],cluster:0,code:6,coher:2,colorscal:2,compon:0,configur:[2,6],cosin:2,cov_avg:2,covari:2,create_imag:2,create_training_class:2,data:[0,3,4,9],depend:6,detect:1,detector:[0,2],dimension:[0,1,4],discrimin:0,displai:[1,4],distanc:2,distribut:6,document:[2,5],ecostress:7,ecostressdatabas:2,entir:3,envi:[2,3,7],erda:[2,3],estim:2,featur:4,file:[1,2,3,4,9],filter:0,fisher:0,fisherlineardiscrimin:2,format:[2,3],fraction:2,from:6,gaussian:0,gaussianclassifi:2,gaussianstat:2,get_rgb:2,git:6,glossari:1,graphic:2,guid:8,header:3,hsi:3,hypercub:4,imag:[3,4],imagearrai:2,imageview:2,imshow:2,index:2,input:1,instal:6,interact:4,interfac:3,introduct:9,ipython:4,iter:2,kmean:2,label:4,lan:[2,3],librari:7,likelihood:0,linear:0,linear_discrimin:2,lineartransform:2,load:3,mahalanobisdistanceclassifi:2,manipul:4,map:4,map_class:2,map_class_id:2,match:0,matched_filt:2,matchedfilt:2,math:1,maximum:0,mean:0,mean_cov:2,memmap:3,minimum:2,miscellan:[0,1],mnf:2,mode:4,msam:2,ndvi:[0,2],nois:2,noise_from_diff:2,numpi:3,open:2,open_imag:2,orthogon:2,output:1,packag:6,perceptronclassifi:2,pip:6,pixel:2,plot:4,ppi:2,princip:0,principal_compon:2,principalcompon:2,puriti:2,python:[5,8],raster:4,read:3,reduct:[0,1],repositori:6,resampl:0,rgb:4,sampl:9,save:4,save_classif:2,save_imag:2,save_rgb:2,sourc:6,spectral:[0,1,2,5,7,8],spectral_angl:2,spectrallibrari:2,spectrum:4,spy:[5,6,8],spyfil:[2,3],spyset:2,start:4,statist:1,subclass:2,subimag:2,supervis:0,support:3,target:[0,1],train:[0,2],trainingclass:2,traningclassset:2,transform:1,transform_imag:2,unsupervis:0,user:[4,8],util:2,view:2,view_cub:2,view_index:2,view_nd:2,visual:1,welcom:5}}) \ No newline at end of file diff --git a/user_guide.html b/user_guide.html new file mode 100644 index 0000000..5a229b2 --- /dev/null +++ b/user_guide.html @@ -0,0 +1,177 @@ + + + + + + + Spectral Python (SPy) User Guide — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/user_guide_intro.html b/user_guide_intro.html new file mode 100644 index 0000000..b0270d2 --- /dev/null +++ b/user_guide_intro.html @@ -0,0 +1,149 @@ + + + + + + + Introduction — Spectral Python 0.21 documentation + + + + + + + + + + + + + + + +
+
+
+
+ +
+

Introduction

+

This user guide introduces various categories of SPy functions in a tutorial +style. If you would like to test the commands presented in the guide, you +should download the following sample data files, which are associated with a +well-studied AVIRIS hyperspectral image collected over Indiana in 1992. +[Landgrebe1998]

+ + ++++ + + + + + + + + + + + + + + + + +
Sample Data Files

File Name

Description

92AV3C.lan

A small hyperspectral image chip (9.3 MB) in ERDAS/Lan format. The chip +is 145x145 pixels from an AVIRIS image and contains 220 spectral bands.

92AV3GT.GIS

A land-use ground-truth map for the hyperspectral image chip in ERDAS/Lan +format.

92AV3C.spc

An AVIRIS-formatted band calibration file for the image chip.

+

Many of the examples presented in the guide are cumulative, with success of +commands issued depending on previous commands and module imports. While it is +generally not a good idea to import the contents of entire module namespaces, +for brevity, the examples in the user guide assume that from spectral import * +has been issued.

+

References

+
+
Landgrebe1998
+

Landgrebe, D. Multispectral data analysis: A signal theory +perspective. School of Electr. Comput. Eng., Purdue Univ., West Lafayette, IN (1998).

+
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + \ No newline at end of file