To achieve this, use the code below, install rasterio, geopandas and shapely modules before running this code.
Code to do the task.
import os
import shapely
import rasterio
import pandas as pd
import geopandas as gpd
import xml.etree.ElementTree as ET
from shapely.geometry import Polygon
#Once the environment and libraries are installed,
#ensure all the XML files are in the directory.
#In this cell, we use the listdir to get all files in the directory,
#and using the if condition, select only the .xml files
all_input_files = [file for file in os.listdir() if file.endswith(".xml")]
#An empty list is created to append all dataframes (each XML file will create one dataframe). This list is used to concatenate all the dataframes into a single dataframe.
conc = []
'''
Step1: In the first for loop we are iterating over all .xml file in the directory .getroot() will return the root element for a particular tree (xml file).
Step2: Access the XML tree (ET=Element Tree), get the root of the tree structure, then from the root get the particular filename of XML.
Step3: Now replace the .png with .tif extension tag, so we can read the same GeoTIFF file and get the spatial data.
Step4: Let us find all bounding boxes and iterate over them to get the CRS for each box. This is a loop inside another loop. Inside the loop, we get the name for each bounding box and bounding box extent.
Step5: Extracting the max and min values of the X and Y-axis. X and Y are different in image space and coordinate space. xmin in the image is Ymin in the coordinate space.
Step6: Getting the point data from X and Y coordinates.
Step7: Create a polygon from the point data generated.
Step8: Creating metadata from shapefiles, after this, the inner loop end, Step 4 to 8 are iterated, all bounding box extents are calculated, and polygon geometry is formed.
Step9: Now, a pandas DataFrame is created, and the data frame is added to the conc list.
'''
# Step 1 # Loop Start
for input_file in all_input_files:
#Step 2
tree = ET.parse(input_file)
root = tree.getroot()
file_name = root.find('filename').text
#Step 3
geotiff_name = file_name.replace('png', 'tif')
data = []
#Step4 Loop start
for el in root.findall('object'):
name = el.find('name').text
bndbox = el.find('bndbox')
#Step5
Ymin = int(bndbox.find('xmin').text)
Ymax = int(bndbox.find('xmax').text)
Xmin = int(bndbox.find('ymin').text)
Xmax = int(bndbox.find('ymax').text)
#Step6
with rasterio.open(geotiff_name) as image:
crs = image.crs
p1 = image.xy(Xmin, Ymin)
p2 = image.xy(Xmax, Ymin)
p3 = image.xy(Xmax, Ymax)
p4 = image.xy(Xmin, Ymax)
#Step 7
poly = shapely.geometry.Polygon([[p[0], p[1]] for p in [p1, p2, p3, p4, p1]])
#Step 8
datadict = {}
datadict['name'] = name
datadict['geometry'] = poly
datadict['image_name'] = file_name
data.append(datadict)
# Inner loop End
#Step 9
df = pd.DataFrame(data)
conc.append(df)
# Loop End
#By using the pandas concatenate, we are merging all the dataframes into a single dataframe.
df = pd.concat(conc)
#Let's use the pandas dataframe, and add the geometry and the CRS to make it a spatial layer.
gdf = gpd.GeoDataFrame(df, geometry='geometry', crs=crs)
#Now the GeoDataFrame is exported as Shapefile.
output_file = 'output.shp'
gdf.to_file(output_file)
print("ShapeFile is successfully created.")