[1]:
# Only flag for single warning if there are repeats
import warnings
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')

from panel_segmentation import utils
import glob
import matplotlib.pyplot as plt
from PIL import Image
import os
from shapely.geometry import Polygon
import geopandas
from mmengine import Config
from mmdet.apis import init_detector, inference_detector
from mmdet.registry import VISUALIZERS
from shapely.geometry import Polygon
import mmcv
import numpy as np
import pandas as pd
import torch
Jupyter environment detected. Enabling Open3D WebVisualizer.
[Open3D INFO] WebRTC GUI backend enabled.
[Open3D INFO] WebRTCWindowSystem: HTTP handshake server disabled.
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\mmengine\optim\optimizer\zero_optimizer.py:11: DeprecationWarning: `TorchScript` support for functional optimizers is deprecated and will be removed in a future PyTorch release. Consider using the `torch.compile` optimizer instead.
  from torch.distributed.optim import \

Hail Damage Detection#

The following example uses the hail detection pipeline to detect hail and calculate its damage on solar installations given a satellite imagery. First, let’s pull some example images down from the hail data examples folder. This data is taken from a November 2023 scan of Austin, TX.

[2]:
example_images = glob.glob("../../../panel_segmentation/examples/austin_hail_data_examples/*.png")
# Ignore images where inferences present
example_images = [img for img in example_images if ".png_0" not in img]

Let’s visualize some of the images beforehand. Several of these images contain PV installations that have visible hail damage.

[3]:
fig, axes = plt.subplots(4, 2, figsize=(10, 20))
axes = axes.ravel()
for i, image_file in enumerate(example_images):
    img = Image.open(image_file)
    axes[i].imshow(img)
    axes[i].axis('off')
plt.tight_layout()
plt.show()
../../_images/examples_extreme_weather_Austin_hail_damage_example_5_0.png

Let’s load in the hail model.

[4]:
# Get hail model and checkpoint
cfg = Config.fromfile("../../../panel_segmentation/models/hail_config.py")
checkpoint_file = "../../../panel_segmentation/models/hail_model.pth"
model = init_detector(cfg, device='cpu')
checkpoint = torch.load(checkpoint_file, map_location='cpu', weights_only=False)
model.load_state_dict(checkpoint['state_dict'])
visualizer = VISUALIZERS.build(model.cfg.visualizer)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\mmdet\apis\inference.py:70: UserWarning: checkpoint is None, use COCO classes by default.
  warnings.warn('checkpoint is None, use COCO classes by default.')

Next, lets run these images through the hail detection algorithms, and visualize our mask outputs.

[5]:
fig, axes = plt.subplots(4, 2, figsize=(10, 20))
axes = axes.ravel()

# Run inference on all of the example images
inference_data_list = list()
results = list()
for i, file in enumerate(example_images):
    img = mmcv.imread(file, channel_order='rgb')
    new_result = inference_detector(model, img)
    visualizer.add_datasample(
          file,
          img,
          data_sample=new_result,
          draw_gt=None,
          wait_time=0,
          pred_score_thr=0.35
          )
    img = visualizer.get_image()
    axes[i].imshow(img)
    axes[i].axis('off')
    new_result.file = file
    results.append(new_result)
plt.tight_layout()
plt.show()
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\mmdet\models\layers\se_layer.py:158: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
  with torch.cuda.amp.autocast(enabled=False):
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\mmdet\models\backbones\csp_darknet.py:118: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
  with torch.cuda.amp.autocast(enabled=False):
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\torch\functional.py:534: UserWarning: torch.meshgrid: in an upcoming release, it will be required to pass the indexing argument. (Triggered internally at C:\cb\pytorch_1000000000000\work\aten\src\ATen\native\TensorShape.cpp:3596.)
  return _VF.meshgrid(tensors, **kwargs)  # type: ignore[attr-defined]
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
../../_images/examples_extreme_weather_Austin_hail_damage_example_9_1.png

Let’s calculate the percentage of hail damage by PV installation.

[6]:
inference_data_list = list()
for result in results:
    predictions = result._pred_instances
    masks = predictions.masks
    labels = predictions.labels
    scores = predictions.scores
    # Take all labels meeting a 0.3 threshold or greater
    idx_to_keep = np.argwhere(scores>=0.3).tolist()[0]
    file_name = os.path.basename(result.file)
    img_center_lat, img_center_lon = float(file_name.split("_")[0]), float(file_name.split("_")[-1].replace(".png", ""))
    image_x_pixels, image_y_pixels = new_result.img_shape
    # Convert all of the masks to latitude-longitude-based polygons
    for index in range(len(masks)):
            if index in idx_to_keep:
                segmentation_mask = masks[index].data.cpu().numpy()
                # Convert the mask to a polygon
                polygon_lat_lon_coords = utils.convertMaskToLatLonPolygon(segmentation_mask,
                                                                    img_center_lat,
                                                                    img_center_lon,
                                                                    image_x_pixels,
                                                                    image_y_pixels,
                                                                    zoom_level=21)
                # Convert to a geoJSON
                shapely_poly = Polygon(polygon_lat_lon_coords)
                geojson_poly = geopandas.GeoSeries(shapely_poly).to_json()
                inference_data_list.append({"image": result.file,
                                            "poly": shapely_poly,
                                            "poly_centroid_lon": shapely_poly.centroid.x,
                                            "poly_centroid_lat": shapely_poly.centroid.y,
                                            "area": shapely_poly.area,
                                            "label": int(labels[index])})
inference_df = pd.DataFrame(inference_data_list)
inference_df.head(10)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
[6]:
image poly poly_centroid_lon poly_centroid_lat area label
0 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.7410372702614 30.14785415189192,... -97.741017 30.147833 2.060606e-09 1
1 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74103191039197 30.14774177215681... -97.741035 30.147719 1.025230e-09 1
2 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74110761874037 30.14772255695745... -97.741107 30.147707 2.214505e-09 1
3 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74256414650021 30.14777612648223... -97.742565 30.147765 5.147609e-10 1
4 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74251590762063 30.14785473411978... -97.742518 30.147821 1.113398e-09 1
5 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74242478948544 30.14783202531050... -97.742412 30.147809 1.524973e-09 1
6 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.7424462290559 30.147865215174217... -97.742438 30.147852 7.031886e-10 1
7 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74256481661757 30.1478553163459,... -97.742562 30.147830 9.637876e-10 1
8 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74238191038515 30.14777903797571... -97.742371 30.147771 3.314054e-10 1
9 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74255945688114 30.14795081001137... -97.742556 30.147928 8.184693e-10 1
[7]:
# Filter to just modules (label == 1)
inference_df_modules = inference_df[inference_df['label'] == 1]
dmg_list = list()

for idx, row in inference_df_modules.iterrows():
    polygon = row['poly']
    # Get all of the potential overlapping modules
    other_masks = inference_df[inference_df['image'].str.contains(os.path.basename(row['image']))]
    other_masks = other_masks[other_masks['label'] == 0]
    total_dmg_amt = 0
    for idx2, row2 in other_masks.iterrows():
        sub_polygon = row2['poly']
        sub_label = row2['label']
        try:
            if sub_polygon.intersects(polygon):
                # Check if the small polygon is entirely within the large polygon.
                # If it's labeled 0, mark it as a duplicate and remove
                if ((sub_polygon.intersection(polygon).area/sub_polygon.area) * 100) > 90:
                    #plt.plot(*sub_polygon.exterior.xy)
                    # if this is a damage flag, check how much overlap there is for damage
                    if sub_label == 0:
                        dmg_pct = ((sub_polygon.area/
                                    polygon.area) * 100)
                        total_dmg_amt = total_dmg_amt + dmg_pct
        except:
            pass
    row_dict = row.to_dict()
    row_dict['total_damage_amt'] = total_dmg_amt
    dmg_list.append(row_dict)

dmg_df = pd.DataFrame(dmg_list)

# Visualize the dataframe, sorted by level of damage
dmg_df.sort_values(by=['total_damage_amt'], ascending=False).head(10)
[7]:
image poly poly_centroid_lon poly_centroid_lat area label total_damage_amt
22 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.6920229528419 30.487146219449322... -97.692075 30.487119 2.618383e-09 1 13.473388
23 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.69199615390984 30.4870713689871,... -97.692045 30.487045 2.605164e-09 1 6.714914
17 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.80183903117377 30.14898632715937... -97.801764 30.148872 8.625823e-09 1 1.368097
18 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.80188794023911 30.14892926447665... -97.801858 30.148806 1.325979e-08 1 0.920872
0 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.7410372702614 30.14785415189192,... -97.741017 30.147833 2.060606e-09 1 0.000000
1 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74103191039197 30.14774177215681... -97.741035 30.147719 1.025230e-09 1 0.000000
2 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74110761874037 30.14772255695745... -97.741107 30.147707 2.214505e-09 1 0.000000
6 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.7424462290559 30.147865215174217... -97.742438 30.147852 7.031886e-10 1 0.000000
5 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74242478948544 30.14783202531050... -97.742412 30.147809 1.524973e-09 1 0.000000
4 ../../../panel_segmentation/examples/austin_ha... POLYGON ((-97.74251590762063 30.14785473411978... -97.742518 30.147821 1.113398e-09 1 0.000000

Let’s visualize the images that were flagged for hail-related damage.

[8]:
dmg_df = dmg_df[dmg_df['total_damage_amt']>0]
dmg_images = list(dmg_df['image'].drop_duplicates())
for img_path in dmg_images:
    img = mmcv.imread(img_path, channel_order='rgb')
    new_result = inference_detector(model, img)
    visualizer.add_datasample(
          img_path,
          img,
          data_sample=new_result,
          draw_gt=None,
          wait_time=0
          )
    img_infer = visualizer.get_image()
    # Create subplots to visualize the original image + inference image side-by-side
    plt.subplot(1, 2, 1)
    img_orig = mmcv.imread(img_path, channel_order='rgb')
    plt.imshow(img_orig)
    plt.subplot(1, 2, 2)
    plt.imshow(img_infer)
    plt.show()
    plt.close()
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\mmdet\models\layers\se_layer.py:158: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
  with torch.cuda.amp.autocast(enabled=False):
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\mmdet\models\backbones\csp_darknet.py:118: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
  with torch.cuda.amp.autocast(enabled=False):
C:\Users\kperry\.conda\envs\deep-learning\Lib\site-packages\numpy\_core\fromnumeric.py:45: DeprecationWarning: __array_wrap__ must accept context and return_scalar arguments (positionally) in the future. (Deprecated NumPy 2.0)
  return conv.wrap(result, to_scalar=False)
../../_images/examples_extreme_weather_Austin_hail_damage_example_14_1.png
../../_images/examples_extreme_weather_Austin_hail_damage_example_14_2.png