{"id":16704245,"url":"https://github.com/aazuspan/aerio","last_synced_at":"2025-04-10T05:05:36.048Z","repository":{"id":55149329,"uuid":"321827564","full_name":"aazuspan/aerio","owner":"aazuspan","description":"Automated processing of historical aerial photography","archived":false,"fork":false,"pushed_at":"2021-01-14T00:22:34.000Z","size":18953,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-24T06:12:06.862Z","etag":null,"topics":["aerial-imagery","fiducials","image-processing","photogrammetry","sfm"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aazuspan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-16T00:51:25.000Z","updated_at":"2024-01-26T16:09:31.000Z","dependencies_parsed_at":"2022-08-14T13:31:15.500Z","dependency_job_id":null,"html_url":"https://github.com/aazuspan/aerio","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aazuspan%2Faerio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aazuspan%2Faerio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aazuspan%2Faerio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aazuspan%2Faerio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aazuspan","download_url":"https://codeload.github.com/aazuspan/aerio/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161280,"owners_count":21057554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aerial-imagery","fiducials","image-processing","photogrammetry","sfm"],"created_at":"2024-10-12T19:11:59.461Z","updated_at":"2025-04-10T05:05:36.019Z","avatar_url":"https://github.com/aazuspan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aerio\n\nAutomated processing of historical aerial photography. Quickly crop images, match histograms, detect fiducials, and mask labels and borders.\n\n## Example\n\n```python\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport os\n\nfrom aerio.Photo import Photo\nfrom aerio.PhotoCollection import PhotoCollection\nfrom aerio.BoundingBoxCollection import BoundingBoxCollection\nfrom aerio import utils\n```\n\n### Loading photos\n\nBefore processing, photos must be loaded into a `PhotoCollection`\n\n```python\n# Select a directory that contains multiple photos\nphoto_dir = os.path.join(\"data\")\n# Generate absolute paths to each photo\nphoto_paths = utils.list_paths(photo_dir, valid_extensions=[\".tif\"])\n# Load all photos in the directory as a Photo Collection\ncollection = PhotoCollection(photo_paths, photo_size=(224, 224))\n```\n\nPhotos within a `PhotoCollection` can be described and previewed\n\n```python\n# Use indexes to select single photos from within a collection\nphoto = collection[0]\n\n# Printing a photo displays various parameters\nprint(photo)\n\n# Previewing a photo displays a thumbnail for visual reference\nphoto.preview(size=(8, 8))\n```\n\n    DWW_3FF_117\n    Resolution (px): 1886 (H) x 1885 (W)\n    DPI: 213.8022\n    Size (mm): 224 (H) x 224 (W)\n    Pixel size (mm): 0.1188 (H) x 0.1188 (W)\n\n![png](README_files/README_6_1.png)\n\n### Pre-processing a photo collection\n\nPre-processing can be used to make sure all photos within a collection are equal size and radiometrically normalized, which can improve aerial triangulation accuracy.\n\n```python\n# Crop all photos to the minimum photo dimensions\ncollection.crop()\n\n# Match histograms for all photos using one of the photos as a reference\ncollection.match_histograms()\n```\n\n### Saving processed photos\n\n```python\n# After processing, photo images can be saved to disk for later use\ncollection.save(path=os.path.join(\"data\", \"processed\"), suffix=\"preprocessed\")\n\n# Photo images can also be extracted as a list of arrays for use outside of aerio\ncollection.images;\n```\n\n### Locating fiducials\n\nFiducial markers may need to be located to perform internal alignment between photos. Currently, `aerio` only supports automatic location of notched fiducials, as shown in this example.\n\n```python\n# Automatically locate the fiducial point in each photo. The size argument indicates\n# the approximate height and width of the fiducial. If fiducials cannot be accurately\n# located, try adjusting the size to include less or more of the fiducial.\ncollection.locate_fiducials(size=(80, 120))\n\n# Individual fiducials can be previewed to confirm location accuracy\ncollection[0].fiducials.bottom.preview()\n```\n\n![png](README_files/README_10_0.png)\n\n```python\n# Coordinates can also be returned as a list of tuples\ncollection[0].fiducials.coordinates\n```\n\n    [(939.0, 54.0), (1832.0, 946.0), (938.0, 1833.0), (53.0, 945.0)]\n\n### Masking\n\nHistorical aerial photos often contain elements such as borders and labels that are not part of the image and may interfere with aerial triangulation. `aerio` contains tools to mask these objects within individual photos using bounding boxes.\n\n#### Manually locating labels\n\nTo mask labels, their bounding boxes must first be located. This can be done manually by entering a list of pixel coordinates. Coordinates can be entered clockwise or counterclockwise. If labels are at the same position in every image, manually locating labels may be worth the hassle.\n\n```python\nphoto = collection[0]\n\n# Boxes can be manually created as a list of pixel coordinates. Coordinates\n# can be entered clockwise or counterclockwise.\nupper_left = [[50, 50], [350, 50], [350, 120], [50, 120]]\nupper_right = [[1400, 50], [1830, 50], [1830, 120], [1400, 120]]\n\nlabels = BoundingBoxCollection([upper_left, upper_right], photo)\n\nlabels.preview()\n```\n\n![png](README_files/README_15_0.png)\n\n#### Automatically locating labels\n\nThere are many techniques for automatically locating text within images. In this example, we will use the `swtloc` library to generate bounding boxes around text. This method can handle labels that are in different positions within different images, but is prone to locating false positive labels and typically requires parameter tuning and post-processing to achieve good results.\n\n```python\nfrom swtloc import SWTLocalizer\n\nswtl = SWTLocalizer()\n\nswtl.swttransform(image=photo.img, save_results=False,\n                  edge_func = 'ac', ac_sigma = 0.4, text_mode = 'db_lf',\n                  gs_blurr=True, blurr_kernel = (15, 15),\n                  minrsw = 5, maxrsw = 15,\n                  minCC_comppx = 50, maxCC_comppx = 1000,\n                  max_angledev = np.pi/6,\n                  acceptCC_aspectratio = 5.)\n\nbbox, _ = swtl.get_extreme_bbox(show=False)\n\n# Turn the lists of coordinates into Bounding Box objects\nlabels = BoundingBoxCollection(bbox, photo)\n# Preview the un-processed bounding boxes. Note the large number of false\n# positive boxes.\nlabels.preview(color=(0, 255, 0))\n```\n\n    Transforming...\n    Operation Completed!\n\n![png](README_files/README_17_1.png)\n\nTo remove false positive labels, `aerio` has tools for cleaning up bounding boxes.\n\n```python\n# Collapse nearby bounding boxes to merge characters. An asymetrical kernel\n# helps to connect horizontally adjacent characters.\nlabels.collapse(kernel=np.ones((5, 15), np.uint8), iterations=20)\n\n# Filter for boxes near the edge of the image that are wider than tall.\nlabels.filter(max_edge_distance = 100, max_hw_ratio=0.5)\n\nlabels.preview(color=(0, 255, 0))\n```\n\n![png](README_files/README_19_0.png)\n\n#### Locating photo borders\n\nAerial photo margins often contain borders that can interfere with aerial triangulation. `aerio` can automatically generate bounding boxes around photo borders. This will only work if the borders are relatively straight and centered.\n\n```python\nborder = photo.border_box(30)\n\nborder.preview()\n```\n\n![png](README_files/README_22_0.png)\n\n#### Combining bounding boxes\n\nAdditional boxes can be added to a `BoundingBoxCollection` by adding a list of coordinates or adding the boxes from another collection. This can be used, for example, to combine label boxes and border boxes into a single collection that can be masked.\n\n```python\ncombined = border + labels\n\ncombined.preview()\n```\n\n![png](README_files/README_24_0.png)\n\n#### Generating masks from boxes\n\nBounding boxes can be converted into mask images for use in other software. Images can be saved directly to file or returned as arrays.\n\n```python\n# Save the mask to file\ncombined.save_mask(path=os.path.join(\"data\", \"masks\"))\n\n# Or return the mask as an array\nmask = combined.generate_mask()\n\n# Preview the mask\n_, ax = plt.subplots(figsize=(8, 8))\nax.imshow(mask, cmap=\"gray\");\n```\n\n![png](README_files/README_25_1.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faazuspan%2Faerio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faazuspan%2Faerio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faazuspan%2Faerio/lists"}