diff --git a/src/deepforest/data/5b90f92da0d7280005fab355_4310.tif b/src/deepforest/data/5b90f92da0d7280005fab355_4310.tif new file mode 100644 index 000000000..1b051d5ab Binary files /dev/null and b/src/deepforest/data/5b90f92da0d7280005fab355_4310.tif differ diff --git a/src/deepforest/data/coco_sample_file.json b/src/deepforest/data/coco_sample_file.json new file mode 100644 index 000000000..9f6983c13 --- /dev/null +++ b/src/deepforest/data/coco_sample_file.json @@ -0,0 +1,1631 @@ +{ + "images": [ + { + "file_name": "5b90f92da0d7280005fab355_4310.tif", + "width": 2048, + "height": 2048, + "id": 2, + "license": 1 + }, + { + "file_name": "5dded5bc0140b80006d2e565_23.tif", + "width": 2048, + "height": 2048, + "id": 3, + "license": 1 + }, + { + "file_name": "5d1cc58493e1130005fc0eb0_2921.tif", + "width": 2048, + "height": 2048, + "id": 1, + "license": 1 + } + ], + "annotations": [ + { + "id": 1, + "image_id": 1, + "category_id": 2, + "segmentation": [ + [ + 1199.29, + 1667.97, + 1202.17, + 1664.7, + 1202.36, + 1662.4, + 1197.99, + 1663.26, + 1196.32, + 1662.84, + 1192.99, + 1657.29, + 1193.27, + 1655.07, + 1198.12, + 1653.68, + 1201.59, + 1651.46, + 1206.59, + 1650.91, + 1211.86, + 1652.99, + 1216.02, + 1656.6, + 1218.52, + 1656.74, + 1223.79, + 1650.77, + 1227.26, + 1648.97, + 1227.54, + 1652.85, + 1220.6, + 1662.01, + 1221.02, + 1664.65, + 1222.68, + 1667.7, + 1222.54, + 1671.44, + 1221.43, + 1672.97, + 1217.69, + 1673.8, + 1212.83, + 1677.13, + 1204.92, + 1677.55, + 1202.15, + 1676.16, + 1199.79, + 1672.83, + 1197.99, + 1668.81, + 1198.4, + 1667.28 + ] + ], + "area": 578.0439500026405, + "bbox": [ + 1192.99, + 1648.97, + 34.549999999999955, + 28.579999999999927 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 2, + "image_id": 1, + "category_id": 2, + "segmentation": [ + [ + 1508.56, + 2011.44, + 1509.41, + 2009.37, + 1511.36, + 2008.27, + 1514.66, + 2009.49, + 1515.88, + 2014.25, + 1514.66, + 2021.57, + 1512.71, + 2024.13, + 1510.51, + 2023.52, + 1508.56, + 2018.15, + 1508.19, + 2012.66 + ] + ], + "area": 90.8636499978602, + "bbox": [ + 1508.19, + 2008.27, + 7.690000000000055, + 15.860000000000127 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 3, + "image_id": 1, + "category_id": 2, + "segmentation": [ + [ + 1472.34, + 1987.58, + 1472.34, + 1983.61, + 1473.04, + 1982.68, + 1478.64, + 1981.04, + 1482.36, + 1990.12, + 1480.04, + 1994.81, + 1476.31, + 1996.21, + 1473.69, + 1993.99, + 1473.5, + 1989.21 + ] + ], + "area": 104.90020000003278, + "bbox": [ + 1472.34, + 1981.04, + 10.019999999999982, + 15.170000000000073 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 4, + "image_id": 1, + "category_id": 2, + "segmentation": [ + [ + 1080.5, + 1627.37, + 1074.29, + 1628.11, + 1072.42, + 1626.0, + 1071.18, + 1620.16, + 1071.31, + 1617.8, + 1072.18, + 1616.93, + 1072.05, + 1618.54, + 1073.92, + 1614.57, + 1076.03, + 1612.33, + 1081.62, + 1611.71, + 1082.32, + 1615.02, + 1082.12, + 1615.31, + 1081.99, + 1617.42, + 1082.99, + 1618.75, + 1082.99, + 1623.39 + ] + ], + "area": 150.59505000151694, + "bbox": [ + 1071.18, + 1611.71, + 11.809999999999945, + 16.399999999999864 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 5, + "image_id": 1, + "category_id": 2, + "segmentation": [ + [ + 150.53, + 32.27, + 155.42, + 31.05, + 158.03, + 31.51, + 161.39, + 33.8, + 162.16, + 38.4, + 162.62, + 41.15, + 160.63, + 44.06, + 153.59, + 46.05, + 151.9, + 44.06, + 149.92, + 38.7, + 149.3, + 35.34, + 150.07, + 33.65 + ] + ], + "area": 149.00364999999874, + "bbox": [ + 149.3, + 31.05, + 13.319999999999993, + 14.999999999999996 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 6, + "image_id": 3, + "category_id": 2, + "segmentation": [ + [ + 1616.0, + 280.11, + 1614.94, + 281.17, + 1609.63, + 273.75, + 1608.57, + 270.57, + 1606.45, + 268.45, + 1603.27, + 262.09, + 1603.27, + 259.96, + 1601.15, + 259.96, + 1600.09, + 258.9, + 1596.91, + 258.9, + 1595.85, + 257.84, + 1587.37, + 257.84, + 1575.7, + 263.15, + 1574.64, + 265.27, + 1569.34, + 268.45, + 1569.34, + 276.93, + 1574.64, + 283.29, + 1578.88, + 285.41, + 1578.88, + 287.54, + 1576.76, + 287.54, + 1574.64, + 288.6, + 1574.64, + 307.68, + 1576.76, + 309.8, + 1584.18, + 311.93, + 1585.24, + 314.05, + 1586.31, + 314.05, + 1586.31, + 328.89, + 1584.18, + 331.01, + 1583.12, + 333.13, + 1583.12, + 335.25, + 1582.06, + 336.31, + 1582.06, + 338.44, + 1581.0, + 339.5, + 1581.0, + 346.92, + 1582.06, + 350.1, + 1589.49, + 355.4, + 1590.55, + 357.52, + 1596.91, + 362.82, + 1596.91, + 370.25, + 1599.03, + 374.49, + 1600.09, + 375.55, + 1606.45, + 377.67, + 1608.57, + 377.67, + 1611.76, + 381.91, + 1611.76, + 382.97, + 1614.94, + 386.15, + 1617.06, + 397.82, + 1619.18, + 399.94, + 1619.18, + 402.06, + 1621.3, + 408.42, + 1624.48, + 411.6, + 1624.48, + 414.78, + 1627.66, + 422.21, + 1630.84, + 425.39, + 1635.08, + 425.39, + 1637.2, + 422.21, + 1644.63, + 419.03, + 1649.93, + 414.78, + 1652.05, + 414.78, + 1660.53, + 409.48, + 1664.78, + 409.48, + 1676.44, + 423.27, + 1679.62, + 425.39, + 1688.1, + 433.87, + 1688.1, + 434.93, + 1693.41, + 439.17, + 1695.53, + 440.23, + 1710.37, + 440.23, + 1713.55, + 439.17, + 1718.86, + 433.87, + 1718.86, + 415.85, + 1719.92, + 413.72, + 1719.92, + 408.42, + 1722.04, + 405.24, + 1722.04, + 403.12, + 1723.1, + 402.06, + 1724.16, + 397.82, + 1727.34, + 393.58, + 1728.4, + 393.58, + 1734.76, + 389.34, + 1737.94, + 389.34, + 1740.06, + 388.27, + 1750.67, + 389.34, + 1754.91, + 391.46, + 1770.82, + 391.46, + 1775.06, + 389.34, + 1782.48, + 382.97, + 1785.66, + 375.55, + 1785.66, + 360.7, + 1786.72, + 358.58, + 1786.72, + 354.34, + 1783.54, + 346.92, + 1782.48, + 341.62, + 1780.36, + 339.5, + 1780.36, + 338.44, + 1769.76, + 329.95, + 1766.58, + 324.65, + 1766.58, + 318.29, + 1772.94, + 310.86, + 1777.18, + 309.8, + 1781.42, + 306.62, + 1784.6, + 305.56, + 1790.96, + 299.2, + 1790.96, + 296.02, + 1788.84, + 291.78, + 1786.72, + 291.78, + 1785.66, + 290.72, + 1776.12, + 288.6, + 1771.88, + 288.6, + 1769.76, + 286.48, + 1766.58, + 286.48, + 1762.33, + 283.29, + 1769.76, + 268.45, + 1769.76, + 263.15, + 1768.7, + 259.96, + 1766.58, + 256.78, + 1765.51, + 256.78, + 1762.33, + 250.42, + 1761.27, + 249.36, + 1759.15, + 249.36, + 1753.85, + 243.0, + 1749.61, + 243.0, + 1746.43, + 241.94, + 1739.0, + 241.94, + 1739.0, + 240.88, + 1734.76, + 236.64, + 1733.7, + 236.64, + 1733.7, + 232.39, + 1731.58, + 231.33, + 1729.46, + 228.15, + 1723.1, + 221.79, + 1719.92, + 221.79, + 1717.8, + 220.73, + 1693.41, + 220.73, + 1689.17, + 223.91, + 1676.44, + 223.91, + 1671.14, + 224.97, + 1660.53, + 230.27, + 1658.41, + 230.27, + 1653.11, + 235.58, + 1649.93, + 239.82, + 1649.93, + 240.88, + 1645.69, + 243.0, + 1644.63, + 245.12, + 1644.63, + 248.3, + 1642.51, + 251.48, + 1640.39, + 256.78, + 1640.39, + 258.9, + 1637.2, + 263.15, + 1636.14, + 266.33, + 1636.14, + 269.51, + 1634.02, + 275.87, + 1631.9, + 279.05, + 1630.84, + 284.35, + 1627.66, + 287.54, + 1624.48, + 286.48, + 1623.42, + 284.35 + ] + ], + "area": 31658.123799994588, + "bbox": [ + 1569.34, + 220.73, + 221.62000000000012, + 219.50000000000003 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 7, + "image_id": 2, + "category_id": 2, + "segmentation": [ + [ + 1559.97, + 1029.3, + 1555.5, + 1029.3, + 1547.23, + 1033.77, + 1543.66, + 1036.68, + 1542.09, + 1040.25, + 1541.65, + 1046.96, + 1542.99, + 1051.2, + 1544.77, + 1055.0, + 1549.91, + 1060.14, + 1559.74, + 1062.82, + 1565.11, + 1063.49, + 1568.46, + 1063.27, + 1570.25, + 1061.93, + 1574.27, + 1052.99, + 1574.27, + 1049.19, + 1572.48, + 1044.05, + 1568.46, + 1034.67 + ] + ], + "area": 844.7273500002921, + "bbox": [ + 1541.65, + 1029.3, + 32.61999999999989, + 34.190000000000055 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 8, + "image_id": 2, + "category_id": 1, + "segmentation": [ + [ + 628.27, + 2047.7, + 626.74, + 2039.12, + 627.3, + 2031.16, + 632.77, + 2027.37, + 636.56, + 2026.11, + 639.93, + 2026.11, + 644.14, + 2030.32, + 645.41, + 2030.32, + 647.51, + 2032.0, + 654.25, + 2032.43, + 657.2, + 2033.69, + 660.99, + 2034.11, + 660.99, + 2034.95, + 662.67, + 2035.37, + 668.99, + 2035.37, + 670.67, + 2031.58, + 671.52, + 2021.06, + 676.15, + 2016.0, + 684.99, + 2011.37, + 714.05, + 2010.95, + 723.31, + 2006.74, + 727.52, + 2003.79, + 736.79, + 2000.0, + 737.63, + 1998.32, + 738.47, + 1998.32, + 738.47, + 1997.05, + 745.21, + 1991.58, + 754.9, + 1987.79, + 761.21, + 1982.74, + 767.11, + 1976.42, + 770.06, + 1974.73, + 775.11, + 1969.68, + 775.95, + 1969.68, + 781.43, + 1963.36, + 784.37, + 1961.26, + 788.58, + 1960.42, + 789.01, + 1959.57, + 798.69, + 1959.15, + 802.48, + 1960.0, + 802.9, + 1961.26, + 809.22, + 1962.94, + 814.27, + 1962.94, + 814.69, + 1960.0, + 815.96, + 1959.15, + 829.43, + 1958.73, + 836.59, + 1954.94, + 840.38, + 1954.94, + 845.43, + 1956.63, + 848.8, + 1956.63, + 854.7, + 1949.47, + 854.7, + 1945.26, + 855.96, + 1941.89, + 853.07, + 1930.74, + 855.29, + 1926.3, + 859.55, + 1920.93, + 861.21, + 1919.82, + 868.79, + 1917.98, + 872.31, + 1918.71, + 877.67, + 1924.82, + 882.29, + 1925.0, + 889.32, + 1928.52, + 891.91, + 1932.03, + 893.39, + 1932.03, + 896.17, + 1930.0, + 898.75, + 1926.3, + 906.71, + 1926.85, + 911.33, + 1922.41, + 916.14, + 1919.27, + 919.84, + 1914.46, + 942.66, + 1896.91, + 943.78, + 1894.31, + 949.35, + 1886.88, + 952.32, + 1884.28, + 952.69, + 1882.42, + 953.81, + 1882.05, + 956.04, + 1879.08, + 961.24, + 1877.22, + 963.84, + 1877.22, + 971.64, + 1880.56, + 974.24, + 1882.05, + 974.24, + 1882.79, + 978.32, + 1886.51, + 982.04, + 1888.36, + 990.21, + 1889.85, + 995.41, + 1892.82, + 997.27, + 1898.39, + 1004.7, + 1908.42, + 1011.76, + 1911.77, + 1013.24, + 1913.25, + 1016.21, + 1913.62, + 1016.21, + 1914.37, + 1019.19, + 1916.22, + 1042.72, + 1914.89, + 1047.51, + 1912.76, + 1050.71, + 1912.76, + 1053.38, + 1915.43, + 1053.91, + 1917.03, + 1059.24, + 1921.29, + 1061.37, + 1921.82, + 1062.44, + 1923.42, + 1064.04, + 1923.42, + 1066.7, + 1925.55, + 1068.3, + 1925.55, + 1072.03, + 1927.68, + 1074.16, + 1927.68, + 1083.22, + 1933.55, + 1086.95, + 1937.81, + 1092.28, + 1941.54, + 1092.28, + 1942.61, + 1093.35, + 1943.14, + 1095.48, + 1946.87, + 1097.08, + 1952.73, + 1096.55, + 1961.79, + 1097.61, + 1964.99, + 1102.94, + 1973.52, + 1102.94, + 1974.58, + 1105.07, + 1976.72, + 1105.07, + 1978.31, + 1107.2, + 1981.51, + 1107.2, + 1983.11, + 1109.34, + 1985.24, + 1109.34, + 1986.31, + 1111.47, + 1987.91, + 1111.47, + 1988.97, + 1113.07, + 1989.51, + 1114.13, + 1991.64, + 1115.73, + 1991.64, + 1118.4, + 1994.3, + 1122.13, + 1995.9, + 1134.92, + 1996.43, + 1143.44, + 1999.63, + 1147.71, + 2002.3, + 1149.84, + 2002.3, + 1153.04, + 2004.43, + 1155.17, + 2004.43, + 1158.9, + 2006.56, + 1163.16, + 2007.63, + 1164.76, + 2009.22, + 1166.36, + 2009.22, + 1166.36, + 2012.42, + 1168.49, + 2014.55, + 1171.16, + 2015.09, + 1171.16, + 2016.15, + 1173.29, + 2018.82, + 1173.29, + 2021.48, + 1175.42, + 2023.61, + 1175.95, + 2035.87, + 1176.49, + 2037.47, + 1178.09, + 2038.54, + 1178.09, + 2040.67, + 1179.68, + 2041.2, + 1182.26, + 2043.83, + 1182.36, + 2047.98 + ] + ], + "area": 49602.306650042534, + "bbox": [ + 626.74, + 1877.22, + 555.6199999999999, + 170.76 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 9, + "image_id": 2, + "category_id": 2, + "segmentation": [ + [ + 364.54, + 1923.07, + 374.78, + 1923.07, + 376.83, + 1923.07, + 378.88, + 1925.12, + 380.93, + 1925.12, + 382.98, + 1927.17, + 382.98, + 1929.22, + 385.02, + 1931.26, + 385.02, + 1937.41, + 385.02, + 1939.46, + 382.98, + 1941.5, + 382.98, + 1943.55, + 382.98, + 1945.6, + 380.93, + 1947.65, + 380.93, + 1949.7, + 378.88, + 1951.74, + 378.88, + 1953.79, + 376.83, + 1955.84, + 374.78, + 1957.89, + 372.74, + 1957.89, + 370.69, + 1959.94, + 368.64, + 1961.98, + 360.45, + 1961.98, + 358.4, + 1961.98, + 356.35, + 1959.94, + 354.3, + 1959.94, + 350.21, + 1955.84, + 348.16, + 1955.84, + 339.97, + 1947.65, + 337.92, + 1945.6, + 337.92, + 1943.55, + 337.92, + 1941.5, + 346.11, + 1933.31, + 348.16, + 1931.26, + 350.21, + 1931.26, + 352.26, + 1929.22, + 354.3, + 1927.17, + 356.35, + 1927.17, + 358.4, + 1925.12, + 360.45, + 1925.12, + 362.5, + 1925.12, + 364.54, + 1923.07 + ] + ], + "area": 1310.7814499996603, + "bbox": [ + 337.92, + 1923.07, + 47.099999999999966, + 38.91000000000008 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 10, + "image_id": 2, + "category_id": 2, + "segmentation": [ + [ + 325.63, + 1355.78, + 344.06, + 1355.78, + 346.11, + 1357.82, + 346.11, + 1359.87, + 348.16, + 1361.92, + 348.16, + 1368.06, + 348.16, + 1370.11, + 350.21, + 1372.16, + 350.21, + 1376.26, + 350.21, + 1378.3, + 352.26, + 1380.35, + 352.26, + 1382.4, + 352.26, + 1384.45, + 354.3, + 1386.5, + 354.3, + 1390.59, + 356.35, + 1392.64, + 356.35, + 1398.78, + 358.4, + 1400.83, + 358.4, + 1409.02, + 358.4, + 1411.07, + 356.35, + 1413.12, + 356.35, + 1415.17, + 356.35, + 1417.22, + 352.26, + 1421.31, + 350.21, + 1423.36, + 348.16, + 1423.36, + 346.11, + 1425.41, + 342.02, + 1425.41, + 339.97, + 1425.41, + 337.92, + 1427.46, + 335.87, + 1427.46, + 333.82, + 1429.5, + 331.78, + 1429.5, + 329.73, + 1431.55, + 325.63, + 1431.55, + 323.58, + 1433.6, + 311.3, + 1433.6, + 309.25, + 1435.65, + 307.2, + 1433.6, + 305.15, + 1435.65, + 299.01, + 1435.65, + 296.96, + 1435.65, + 294.91, + 1433.6, + 292.86, + 1433.6, + 290.82, + 1433.6, + 286.72, + 1429.5, + 284.67, + 1427.46, + 284.67, + 1419.26, + 282.62, + 1417.22, + 282.62, + 1404.93, + 280.58, + 1402.88, + 280.58, + 1392.64, + 278.53, + 1390.59, + 278.53, + 1376.26, + 278.53, + 1374.21, + 280.58, + 1372.16, + 280.58, + 1370.11, + 282.62, + 1370.11, + 284.67, + 1368.06, + 286.72, + 1366.02, + 290.82, + 1366.02, + 292.86, + 1366.02, + 294.91, + 1363.97, + 301.06, + 1363.97, + 303.1, + 1363.97, + 305.15, + 1361.92, + 309.25, + 1361.92, + 311.3, + 1361.92, + 313.34, + 1359.87, + 315.39, + 1359.87, + 317.44, + 1359.87, + 319.49, + 1357.82, + 321.54, + 1357.82, + 323.58, + 1357.82, + 325.63, + 1355.78 + ] + ], + "area": 5087.623199999332, + "bbox": [ + 278.53, + 1355.78, + 79.87, + 79.87000000000012 + ], + "iscrowd": 0, + "extra": {} + }, + { + "id": 11, + "image_id": 2, + "category_id": 1, + "segmentation": [ + [ + 294.26, + 1198.13, + 291.65, + 1195.0, + 284.6, + 1192.65, + 277.03, + 1187.17, + 270.24, + 1186.65, + 266.59, + 1187.69, + 261.63, + 1187.69, + 251.19, + 1183.0, + 244.41, + 1175.69, + 242.58, + 1174.91, + 241.54, + 1173.08, + 161.15, + 1118.01, + 51.79, + 1046.46, + 38.43, + 1023.21, + 31.94, + 1021.91, + 31.75, + 1019.12, + 31.61, + 1019.01, + 32.77, + 1017.86, + 45.06, + 1017.86, + 47.1, + 1019.9, + 53.25, + 1019.9, + 55.3, + 1021.95, + 65.54, + 1021.95, + 67.58, + 1019.9, + 75.78, + 1019.9, + 77.82, + 1017.86, + 79.87, + 1017.86, + 81.92, + 1019.9, + 83.97, + 1019.9, + 98.3, + 1034.24, + 98.3, + 1036.29, + 108.54, + 1046.53, + 110.59, + 1046.53, + 112.64, + 1048.58, + 114.69, + 1048.58, + 118.78, + 1052.67, + 122.88, + 1052.67, + 124.93, + 1054.72, + 135.17, + 1054.72, + 137.22, + 1056.77, + 149.5, + 1056.77, + 151.55, + 1058.82, + 172.03, + 1058.82, + 184.32, + 1071.1, + 184.32, + 1073.15, + 188.42, + 1077.25, + 190.46, + 1077.25, + 194.56, + 1081.34, + 204.8, + 1081.34, + 206.85, + 1079.3, + 208.9, + 1079.3, + 208.9, + 1077.25, + 210.94, + 1075.2, + 210.94, + 1034.24, + 208.9, + 1032.19, + 208.9, + 1028.1, + 206.85, + 1026.05, + 206.85, + 1017.86, + 204.8, + 1015.81, + 204.8, + 1005.57, + 202.75, + 1003.52, + 202.75, + 1001.47, + 200.7, + 999.42, + 200.7, + 997.38, + 198.66, + 995.33, + 198.66, + 989.18, + 196.61, + 987.14, + 196.61, + 985.09, + 194.56, + 983.04, + 192.51, + 983.04, + 190.46, + 980.99, + 190.46, + 978.94, + 188.42, + 976.9, + 186.37, + 976.9, + 176.13, + 966.66, + 176.13, + 964.61, + 174.08, + 962.56, + 174.08, + 960.51, + 172.03, + 958.46, + 172.03, + 956.42, + 167.94, + 952.32, + 167.94, + 950.27, + 163.84, + 946.18, + 163.84, + 944.13, + 159.74, + 940.03, + 159.74, + 937.98, + 155.65, + 933.89, + 155.65, + 931.84, + 151.55, + 927.74, + 151.55, + 925.7, + 143.36, + 917.5, + 143.36, + 915.46, + 137.22, + 909.31, + 137.22, + 907.26, + 135.17, + 905.22, + 135.17, + 903.17, + 124.93, + 892.93, + 122.88, + 892.93, + 120.83, + 890.88, + 96.26, + 890.88, + 94.21, + 888.83, + 88.06, + 888.83, + 86.02, + 886.78, + 79.87, + 886.78, + 77.82, + 884.74, + 55.3, + 884.74, + 49.15, + 890.88, + 47.1, + 890.88, + 40.96, + 897.02, + 38.91, + 897.02, + 36.86, + 899.07, + 32.77, + 899.07, + 28.67, + 903.17, + 26.62, + 903.17, + 22.53, + 907.26, + 20.48, + 907.26, + 8.19, + 919.55, + 8.19, + 921.6, + 6.14, + 923.65, + 6.14, + 927.74, + 2.05, + 931.84, + 2.05, + 935.94, + 0.0, + 937.98, + 0.0, + 1015.81, + 0.02, + 1015.83, + 0.02, + 1112.34, + 12.99, + 1111.36, + 13.37, + 1107.95, + 15.26, + 1104.16, + 20.19, + 1098.1, + 22.47, + 1092.41, + 29.67, + 1079.9, + 31.18, + 1075.73, + 32.21, + 1067.14, + 33.57, + 1062.88, + 34.73, + 1060.95, + 37.05, + 1059.4, + 37.24, + 1063.66, + 36.27, + 1066.37, + 36.08, + 1071.78, + 37.43, + 1072.94, + 38.98, + 1077.2, + 48.26, + 1092.28, + 60.55, + 1093.22, + 76.86, + 1100.91, + 84.55, + 1106.74, + 89.45, + 1108.84, + 99.7, + 1115.83, + 108.33, + 1125.85, + 116.95, + 1133.31, + 138.45, + 1144.82, + 142.1, + 1145.94, + 148.84, + 1150.43, + 153.34, + 1154.65, + 160.08, + 1157.17, + 165.7, + 1160.54, + 170.19, + 1166.16, + 173.84, + 1169.25, + 186.66, + 1176.67, + 187.53, + 1183.29, + 191.27, + 1191.64, + 194.72, + 1195.09, + 198.46, + 1197.4, + 202.49, + 1199.41, + 214.58, + 1199.99, + 226.96, + 1208.91, + 230.99, + 1213.23, + 238.76, + 1216.97, + 256.32, + 1227.91, + 260.63, + 1229.34, + 270.71, + 1229.34, + 273.3, + 1228.48, + 277.9, + 1225.31, + 283.37, + 1223.3, + 284.81, + 1220.71, + 284.81, + 1218.98, + 286.54, + 1216.97, + 292.58, + 1215.53, + 292.87, + 1204.59 + ] + ], + "area": 39682.60075000115, + "bbox": [ + 0.0, + 884.74, + 294.26, + 344.5999999999999 + ], + "iscrowd": 0, + "extra": {} + } + ], + "categories": [ + { + "id": 2, + "name": "tree", + "supercategory": "root" + }, + { + "id": 1, + "name": "canopy", + "supercategory": "root" + } + ] +} diff --git a/src/deepforest/datasets/training.py b/src/deepforest/datasets/training.py index 7ed188f45..71e08135f 100644 --- a/src/deepforest/datasets/training.py +++ b/src/deepforest/datasets/training.py @@ -5,6 +5,7 @@ from abc import abstractmethod from typing import Any +import cv2 import kornia.augmentation as K import numpy as np import shapely @@ -560,6 +561,220 @@ def __getitem__(self, idx) -> tuple: return image, targets, self.image_names[idx] +class PolygonDataset(TrainingDataset): + _data_keys = [DataKey.IMAGE, DataKey.BBOX_XYXY, DataKey.MASK] + + def __init__( + self, + annotation_file, + root_dir, + transforms=None, + augmentations=None, + label_dict=None, + preload_images=None, + ): + super().__init__( + csv_file=annotation_file, + root_dir=root_dir, + transforms=transforms, + augmentations=augmentations, + label_dict=label_dict, + preload_images=preload_images, + ) + + def _validate_coordinates(self) -> None: + """Validate that all points occur within the image. + + Raises: + ValueError: If any point occurs outside the image + """ + errors = [] + for _, row in self.annotations.iterrows(): + img_path = os.path.join(self.root_dir, row["image_path"]) + try: + with Image.open(img_path) as img: + width, height = img.size + except Exception as e: + errors.append(f"Failed to open image {img_path}: {e}") + continue + + self._validate_segmentation_coordinates(row, errors, img_path, width, height) + self._validate_box_coordinates(row, errors, img_path, width, height) + + if errors: + raise ValueError("\n".join(errors)) + + def _validate_segmentation_coordinates(self, row, errors, img_path, width, height): + coords = [] + oob_issues = [] + geom = row["geometry"] + + if geom.area == 0: + errors.append(f"Polygon has zero area for image {img_path}") + + if not geom.is_valid: + errors.append("Invalid polygon (self-intersecting)") + + if geom.geom_type == "Polygon": + coords.extend(list(geom.exterior.coords)) + elif geom.geom_type == "MultiPolygon": + for poly in geom.geoms: + coords.extend(list(poly.exterior.coords)) + + if len(set(coords)) < 3: + errors.append("Polygon has fewer than 3 unique points") + + for x, y in coords: + if x < 0: + oob_issues.append(f"x ({x}) < 0") + if x > width: + oob_issues.append(f"x ({x}) > image width ({width})") + if y < 0: + oob_issues.append(f"y ({y}) < 0") + if y > height: + oob_issues.append(f"y ({y}) > image height ({height})") + + if oob_issues: + errors.append( + f"Point, ({x}, {y}) exceeds image dimensions, ({width}, {height}) for {img_path}. Issues: {', '.join(oob_issues)}." + ) + + def _validate_box_coordinates(self, row, errors, img_path, width, height): + xmin, ymin, xmax, ymax = row["xmin"], row["ymin"], row["xmax"], row["ymax"] + oob_issues = [] + if xmin < 0: + oob_issues.append(f"xmin ({xmin}) < 0") + if xmax > width: + oob_issues.append(f"xmax ({xmax}) > image width ({width})") + if ymin < 0: + oob_issues.append(f"ymin ({ymin}) < 0") + if ymax > height: + oob_issues.append(f"ymax ({ymax}) > image height ({height})") + if oob_issues: + errors.append( + f"Box, ({xmin}, {ymin}, {xmax}, {ymax}) exceeds image dimensions, ({width}, {height}) for {img_path}. Issues: {', '.join(oob_issues)}." + ) + + def generate_mask(self, image_annotation, width, height): + mask = np.zeros((height, width), dtype=np.uint8) + geom = image_annotation["geometry"] + + if isinstance(geom, str): + geom = shapely.wkt.loads(geom) + + # ------------------------- + # POLYGON + # ------------------------- + if geom.geom_type == "Polygon": + coords = np.array(geom.exterior.coords, dtype=np.int32) + cv2.fillPoly(mask, [coords], color=255) + + # ------------------------- + # MULTIPOLYGON + # ------------------------- + elif geom.geom_type == "MultiPolygon": + for poly in geom.geoms: + if not poly.is_empty: + coords = np.array(poly.exterior.coords, dtype=np.int32) + cv2.fillPoly(mask, [coords], color=255) + return mask + + def annotations_for_path(self, image_name, image_path, return_tensor=False): + try: + print("opening image") + with Image.open(image_path) as img: + width, height = img.size + except Exception as e: + print(f"Failed to open image {image_path}: {e}") + return + image_annotations = self.annotations[self.annotations.image_path == image_name] + targets = {} + boxes = [] + labels = [] + masks = [] + iscrowd = [] + area = [] + + for _, ann in image_annotations.iterrows(): + boxes.append([ann["xmin"], ann["ymin"], ann["xmax"], ann["ymax"]]) + labels.append(self.label_dict[ann["label"]]) + mask = self.generate_mask(ann, width, height) + masks.append(mask) + iscrowd.append(ann["iscrowd"]) + area.append(ann["area"]) + + if return_tensor: + boxes = torch.as_tensor(boxes, dtype=torch.float32) + labels = torch.as_tensor(labels, dtype=torch.int64) + if len(masks) > 0: + masks = np.stack(masks) + masks = (masks > 0).astype(np.uint8) + masks = torch.as_tensor(masks, dtype=torch.uint8) + else: + masks = torch.zeros((0, height, width), dtype=torch.uint8) + iscrowd = torch.as_tensor(iscrowd, dtype=torch.bool) + area = torch.as_tensor(area) + + image_id = image_annotations.iloc[0]["image_id"] + + targets = { + "image_id": image_id, + "boxes": boxes, + "labels": labels, + "masks": masks, + "iscrowd": iscrowd, + "area": area, + } + + return targets + + def __getitem__(self, index): + image_id = self.image_names[index] + image_path = os.path.join(self.root_dir, image_id) + image = Image.open(image_path).convert("RGB") + width, height = image.size + + targets = self.annotations_for_path(self.image_names[index], image_path) + + # Apply augmentations + np_image = np.array(image) + image_tensor = ( + torch.from_numpy(np_image).permute(2, 0, 1).unsqueeze(0).float() / 255.0 + ) + boxes_tensor = torch.as_tensor(targets["boxes"], dtype=torch.float32).unsqueeze(0) + masks = targets["masks"] + if len(masks) > 0: + masks = np.stack(masks) + masks = (masks > 0).astype(np.uint8) + masks = torch.as_tensor(masks, dtype=torch.uint8).unsqueeze(0) + else: + masks = torch.zeros((0, height, width), dtype=torch.uint8).unsqueeze(0) + + augmented_image, augmented_boxes, augmented_masks = self.transform( + image_tensor, boxes_tensor, masks + ) + + # Convert to tensor + image = augmented_image.squeeze() + boxes = augmented_boxes.squeeze() + masks = augmented_masks.squeeze() + labels = torch.as_tensor(targets["labels"], dtype=torch.int64) + iscrowd = torch.as_tensor(targets["iscrowd"], dtype=torch.bool) + area = torch.as_tensor(targets["area"]) + image_id = targets["image_id"] + + targets = { + "image_id": image_id, + "boxes": boxes, + "labels": labels, + "masks": masks, + "iscrowd": iscrowd, + "area": area, + } + + return image, targets + + # ---------- ImageFolder alignment utilities ---------- diff --git a/src/deepforest/utilities.py b/src/deepforest/utilities.py index f467bbc6c..eb484742c 100644 --- a/src/deepforest/utilities.py +++ b/src/deepforest/utilities.py @@ -417,47 +417,79 @@ def format_boxes(prediction, scores=True): def read_coco(json_file): - """Read a COCO format JSON file and return a pandas dataframe. - - Args: - json_file: Path to the COCO segmentation JSON file - Returns: - df: A pandas dataframe with image_path, geometry, and label columns - """ with open(json_file) as f: - coco_data = json.load(f) + coco = json.load(f) + + # Precompute mappings + image_ids = {img["id"]: img["file_name"] for img in coco["images"]} + cat_map = {cat["id"]: cat["name"] for cat in coco["categories"]} + + # Local references + annotations = coco["annotations"] + get_filename = image_ids.get + get_label = cat_map.get + + # Output containers + data = { + "image_id": [], + "image_path": [], + "iscrowd": [], + "geometry": [], + "xmin": [], + "ymin": [], + "xmax": [], + "ymax": [], + "area": [], + "label": [], + } - # Create mapping from image IDs to filenames - image_ids = {image["id"]: image["file_name"] for image in coco_data["images"]} + # Pre-bind methods + append = {k: v.append for k, v in data.items()} - # Create mapping from category IDs to category names - category_id_to_name = { - category["id"]: category["name"] for category in coco_data["categories"] - } + for ann in annotations: + segmentation = ann.get("segmentation") + + if not isinstance(segmentation, list): + continue - polygons = [] - filenames = [] - labels = [] + poly_list = [] - for annotation in coco_data["annotations"]: - segmentation = annotation.get("segmentation") - if not segmentation: + for seg in segmentation: + coords = list(zip(seg[::2], seg[1::2], strict=True)) + + if len(coords) < 3: + continue + + poly = shapely.geometry.Polygon(coords) + + if poly.is_valid and not poly.is_empty: + poly_list.append(poly) + + if not poly_list: continue - # COCO polygons are usually a list of lists; take the first (assume "single part") - segmentation_mask = segmentation[0] - # Convert flat list to coordinate pairs - pairs = [ - (segmentation_mask[i], segmentation_mask[i + 1]) - for i in range(0, len(segmentation_mask), 2) - ] - polygon = shapely.geometry.Polygon(pairs) - filenames.append(image_ids[annotation["image_id"]]) - polygons.append(polygon.wkt) - cat_id = annotation.get("category_id") - label = category_id_to_name.get(cat_id, cat_id) - labels.append(label) - - return pd.DataFrame({"image_path": filenames, "geometry": polygons, "label": labels}) + + merged_poly = ( + poly_list[0] if len(poly_list) == 1 else shapely.ops.unary_union(poly_list) + ) + + # Extract bbox + xmin, ymin, w, h = ann["bbox"] + xmax = xmin + w + ymax = ymin + h + + # Append values + append["image_id"](ann["image_id"]) + append["image_path"](get_filename(ann["image_id"])) + append["iscrowd"](ann.get("iscrowd", 0)) + append["geometry"](merged_poly.wkt) + append["xmin"](xmin) + append["ymin"](ymin) + append["xmax"](xmax) + append["ymax"](ymax) + append["area"](ann.get("area", 0)) + append["label"](get_label(ann.get("category_id"))) + + return pd.DataFrame(data) def __pandas_to_geodataframe__(df: pd.DataFrame): diff --git a/tests/test_datasets_training_polygons.py b/tests/test_datasets_training_polygons.py new file mode 100644 index 000000000..af9d7feae --- /dev/null +++ b/tests/test_datasets_training_polygons.py @@ -0,0 +1,147 @@ +# Test polygon dataset model +import os +import numpy as np +import json +import pytest +import torch +from PIL import Image + +from deepforest import get_data +from deepforest.datasets.training import PolygonDataset + + +@pytest.fixture() +def polygon_annotation_file(): + return get_data("coco_sample_file.json") + + +@pytest.fixture() +def polygon_root_dir(): + return os.path.dirname(get_data("coco_sample_file.json")) + + +def test_polygon_dataset_basic(polygon_annotation_file, polygon_root_dir): + ds = PolygonDataset( + annotation_file=polygon_annotation_file, + root_dir=polygon_root_dir, + label_dict={"tree": 0, "canopy": 1}, + ) + + image, targets = ds[0] + + # Image checks + assert torch.is_tensor(image) + assert image.shape[0] == 3 + assert image.min() >= 0 + assert image.max() <= 1 + + # Targets checks + assert "boxes" in targets + assert "labels" in targets + assert "masks" in targets + assert "image_id" in targets + assert "iscrowd" in targets + assert "area" in targets + + assert targets["boxes"].shape[-1] == 4 + assert targets["labels"].dtype == torch.int64 + assert targets["masks"].dtype == torch.uint8 + + +def test_generate_mask_polygon(polygon_annotation_file, polygon_root_dir): + ds = PolygonDataset( + annotation_file=polygon_annotation_file, + root_dir=polygon_root_dir, + label_dict={"tree": 0, "canopy": 1}, + ) + + row = ds.annotations.iloc[0] + image_path = os.path.join(ds.root_dir, row["image_path"]) + + with Image.open(image_path) as img: + width, height = img.size + mask = ds.generate_mask(row, width=width, height=height) + + assert mask.shape == (height, width) + assert mask.dtype == np.uint8 + assert mask.sum() > 0 + + +def test_annotations_for_path_tensor(polygon_annotation_file, polygon_root_dir): + ds = PolygonDataset( + annotation_file=polygon_annotation_file, + root_dir=polygon_root_dir, + label_dict={"tree": 0, "canopy": 1}, + ) + + image_name = ds.image_names[0] + image_path = os.path.join(polygon_root_dir, image_name) + + targets = ds.annotations_for_path(image_name, image_path, return_tensor=True) + + assert torch.is_tensor(targets["boxes"]) + assert torch.is_tensor(targets["labels"]) + assert torch.is_tensor(targets["masks"]) + + assert targets["boxes"].shape[-1] == 4 + assert targets["masks"].ndim == 3 + + +def test_polygon_oob_coordinates(tmp_path, polygon_root_dir): + image_name = "5b90f92da0d7280005fab355_4310.tif" + data = { + "images": [{"id": 1, "file_name": image_name, "width": 2048, "height": 2048}], + "annotations": [ + { + "id": 1, + "image_id": 1, + "category_id": 1, + "segmentation": [[2049, 10, 2500, 10, 2048.1, 50, 200, 50]], + "bbox": [200, 10, 50, 40], + "area": 2000, + "iscrowd": 0, + } + ], + "categories": [{"id": 1, "name": "tree"}], + } + + json_path = tmp_path / "oob.json" + with open(json_path, "w") as f: + json.dump(data, f) + + with pytest.raises(ValueError): + PolygonDataset( + annotation_file=str(json_path), + root_dir=polygon_root_dir, + label_dict={"tree": 0, "canopy": 1}, + ) + + +def test_polygon_negative_coordinates(tmp_path, polygon_root_dir): + image_name = "5b90f92da0d7280005fab355_4310.tif" + data = { + "images": [{"id": 1, "file_name": image_name, "width": 2048, "height": 2048}], + "annotations": [ + { + "id": 1, + "image_id": 1, + "category_id": 1, + "segmentation": [[-10, 10, 50, 10, 50, 50, -10, 50]], + "bbox": [-10, 10, 50, 50], + "area": 1600, + "iscrowd": 0, + } + ], + "categories": [{"id": 1, "name": "tree"}], + } + + json_path = tmp_path / "neg.json" + with open(json_path, "w") as f: + json.dump(data, f) + + with pytest.raises(ValueError): + PolygonDataset( + annotation_file=str(json_path), + root_dir=polygon_root_dir, + label_dict={"tree": 0, "canopy": 1}, + ) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 0be8a665b..34e0667fa 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -574,12 +574,18 @@ def test_read_coco_json(tmp_path): { "image_id": 1, "segmentation": [[0, 0, 0, 10, 10, 10, 10, 0]], # Simple square - "category_id": 0 + "category_id": 0, + "bbox": [0,0,10,10], + "area": 100, + "iscrowd": 0 }, { "image_id": 2, "segmentation": [[5, 5, 5, 15, 15, 15, 15, 5]], # Another square - "category_id": 1 + "category_id": 1, + "bbox": [5,5,10,10], + "area": 100, + "iscrowd": 0 } ] } @@ -594,15 +600,27 @@ def test_read_coco_json(tmp_path): # Assert the dataframe has the expected structure assert df.shape[0] == 2 # Two annotations - assert "image_path" in df.columns - assert "geometry" in df.columns - assert "label" in df.columns - assert hasattr(df, "root_dir") + + expected_cols = { + "image_id", "image_path", "iscrowd", "geometry", + "xmin", "ymin", "xmax", "ymax", "area", "label" + } + assert expected_cols.issubset(df.columns) # Check the image paths are correct assert "OSBS_029.png" in df.image_path.values assert "OSBS_029.tif" in df.image_path.values + # Check labels + assert set(df["label"]) == {"Tree", "Bird"} + + # Check bbox correctness + row = df[df["image_id"] == 1].iloc[0] + assert row["xmin"] == 0 + assert row["ymin"] == 0 + assert row["xmax"] == 10 + assert row["ymax"] == 10 + # Verify the geometries are valid polygons for geom in df.geometry: assert geom.is_valid