Skip to main content

Interface to tile sonar mosaics and maps.

Project description

PINGTile

PyPI - Version

Utility to tile sonar mosaics and maps.

UNDER CONSTRUCTION

Check back soon....

Installation

  1. Install Miniforge.
  2. Open the Miniforge prompt.
  3. Install PINGInstaller:
    pip install pinginstaller
    
  4. Install PINGTile.
    python -m pinginstaller pingtile
    

Usage

  1. Copy the following script to some location on your computer:
'''
Copyright (c) 2025 Cameron S. Bodine
'''

#########
# Imports

import os, sys
from joblib import Parallel, delayed, cpu_count

# Debug
from imglbl2tile import doImgLbl2tile
from utils import mask_to_coco_json

# # For Package
# from pingtile.imglbl2tile import doImgLbl2tile
# from pingtile.utils import mask_to_coco_json

import rasterio as rio
import json

############
# Parameters

map = r'Z:\tmp\pingtile_test\map\Model_Training_Substrate_Polygons_Export.shp'
sonarDir = r'Z:\tmp\pingtile_test\mosaic'

outDirTop = r'Z:\tmp\pingtile_test'
outName = 'Hudson'

classCrossWalk = {
    '0':0,
    'U':1,
    'G':2,
    'B_C':3,
    'B':4
}

windowSize_m = [
                (12,12),
                (18,18),
                (24,24),
                ]

windowStride = 3
classFieldName = 'Substrate_'
minArea_percent = 0.5
target_size = (512, 512) #(1024, 1024)
threadCnt = 0.75
epsg_out = 32616
doPlot = True
lbl2COCO = True

if not os.path.exists(outDirTop):
    os.makedirs(outDirTop)


###############################################
# Specify multithreaded processing thread count
if threadCnt==0: # Use all threads
    threadCnt=cpu_count()
elif threadCnt<0: # Use all threads except threadCnt; i.e., (cpu_count + (-threadCnt))
    threadCnt=cpu_count()+threadCnt
    if threadCnt<0: # Make sure not negative
        threadCnt=1
elif threadCnt<1: # Use proportion of available threads
    threadCnt = int(cpu_count()*threadCnt)
    # Make even number
    if threadCnt % 2 == 1:
        threadCnt -= 1
else: # Use specified threadCnt if positive
    pass

if threadCnt>cpu_count(): # If more than total avail. threads, make cpu_count()
    threadCnt=cpu_count();
    print("\nWARNING: Specified more process threads then available, \nusing {} threads instead.".format(threadCnt))

print("\nUsing {} threads for processing.\n".format(threadCnt))


# Find all sonar files
sonarFiles = []
for root, dirs, files in os.walk(sonarDir):
    for file in files:
        if file.lower().endswith('.tif') or file.lower().endswith('.tiff'):
            sonarFiles.append(os.path.join(root, file))


for windowSize in windowSize_m:

    # windowStride_m = windowStride*windowSize[0]
    windowStride_m = windowStride
    # minArea = minArea_percent * windowSize[0]*windowSize[1]

    dirName = f"{windowSize[0]}_{windowSize[0]}"
    outDir = os.path.join(outDirTop, dirName)
    outSonDir = os.path.join(outDir, 'images')
    outMaskDir = os.path.join(outDir,'labels')
    pltDir = os.path.join(outDir,'plots')

    if not os.path.exists(outSonDir):
        os.makedirs(outSonDir)
        os.makedirs(outMaskDir)
        os.makedirs(pltDir)

    for sonarFile in sonarFiles:

        print(f"\nProcessing {os.path.basename(sonarFile)} with windowSize: {windowSize} and windowStride_m: {windowStride_m}...\n")

        doImgLbl2tile(inFileSonar=sonarFile,
                      inFileMask=map,
                      outDir=outDir,
                      outName=outName,
                      epsg_out=epsg_out,
                      classCrossWalk=classCrossWalk,
                      windowSize=windowSize,
                      windowStride_m=windowStride_m,
                      classFieldName=classFieldName,
                      minArea_percent=minArea_percent,
                      target_size=target_size,
                      threadCnt=threadCnt,
                      doPlot=doPlot
                      )

# Convert masks to COCO format
if lbl2COCO:
    

    for windowSize in windowSize_m:

        dirName = f"{windowSize[0]}_{windowSize[0]}"
        outDir = os.path.join(outDirTop, dirName)
        outSonDir = os.path.join(outDir, 'images')
        outMaskDir = os.path.join(outDir,'labels')
        pltDir = os.path.join(outDir,'plots')
        outJsonDir = os.path.join(outDir,'json')

        if not os.path.exists(outJsonDir):
            os.makedirs(outJsonDir)

        print(f"\nConverting to COCO format for windowSize: {windowSize}...\n")

        # Get the mask files
        maskFiles = []
        for root, dirs, files in os.walk(outMaskDir):
            for file in files:
                if file.lower().endswith(('.tif', '.tiff', '.png', '.jpg', '.jpeg')):
                    maskFiles.append(os.path.join(root, file))

        maskFiles=maskFiles[:10] # Debug limit to 10 files

        # Build categories list / lookup from classCrossWalk
        # categories_info passed to mask_to_coco_json should map id -> name
        categories_info = {v: str(k) for k, v in classCrossWalk.items()}
        # COCO categories (exclude background id 0 if present)
        categories = [{"id": v, "name": str(k)} for k, v in classCrossWalk.items() if v != 0]

        coco = {
            "info": {"description": outName or ""},
            "licenses": [],
            "images": [],
            "annotations": [],
            "categories": categories
        }

        annotation_id = 1
        image_id = 1

        for mask_path in maskFiles:
            base = os.path.splitext(os.path.basename(mask_path))[0]

            # try to find corresponding image filename in images folder (same base name)
            matched_image = None
            for ext in ('.png', '.jpg', '.jpeg', '.tif', '.tiff'):
                candidate = os.path.join(outSonDir, base + ext)
                if os.path.exists(candidate):
                    matched_image = os.path.basename(candidate)
                    break
            if matched_image is None:
                # fallback to mask basename (acceptable as file_name in COCO)
                matched_image = os.path.basename(mask_path)

            # read mask to get width/height
            try:
                with rio.open(mask_path) as src:
                    width, height = src.width, src.height
            except Exception as e:
                print(f"Skipping {mask_path}: cannot read ({e})")
                continue

            image_info = {
                "id": image_id,
                "file_name": matched_image,
                "width": width,
                "height": height
            }

            # mask_to_coco_json should return (annotations_list, next_annotation_id)
            anns, annotation_id = mask_to_coco_json(mask_path, image_info, categories_info, annotation_id)

            if anns:
                coco["images"].append(image_info)
                coco["annotations"].extend(anns)
                image_id += 1

        out_json = os.path.join(outJsonDir, f"_annotations.coco.json")
        with open(out_json, "w") as f:
            json.dump(coco, f)
  1. Open the file with Visual Studio Code.
  2. Update the Parameters as necessary:
############
# Parameters

map = r'Z:\tmp\pingtile_test\map\Model_Training_Substrate_Polygons_Export.shp'
sonarDir = r'Z:\tmp\pingtile_test\mosaic'

outDirTop = r'Z:\tmp\pingtile_test'
outName = 'Hudson'

classCrossWalk = {
    '0':0,
    'U':1,
    'G':2,
    'B_C':3,
    'B':4
}

windowSize_m = [
                (12,12),
                (18,18),
                (24,24),
                ]

windowStride = 3
classFieldName = 'Substrate_'
minArea_percent = 0.5
target_size = (512, 512) #(1024, 1024)
threadCnt = 0.75
epsg_out = 32616
doPlot = True
lbl2COCO = True
  1. Ensure the pingtile environment is selected as the Interpreter see this.
  2. Run the script in debug mode by pressing F5.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pingtile-0.0.1a4.tar.gz (18.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pingtile-0.0.1a4-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file pingtile-0.0.1a4.tar.gz.

File metadata

  • Download URL: pingtile-0.0.1a4.tar.gz
  • Upload date:
  • Size: 18.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for pingtile-0.0.1a4.tar.gz
Algorithm Hash digest
SHA256 00df8f1cd827e1155bc045742c8b14350ee9ca0a9486bfa99f7acc71c316b572
MD5 b37adc3baca2dbcfdee53b90d8300f4d
BLAKE2b-256 19b224e9890f8077f723a5199dc9d3c33180af3b107ffc91351012adfa2d2f4d

See more details on using hashes here.

File details

Details for the file pingtile-0.0.1a4-py3-none-any.whl.

File metadata

  • Download URL: pingtile-0.0.1a4-py3-none-any.whl
  • Upload date:
  • Size: 19.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for pingtile-0.0.1a4-py3-none-any.whl
Algorithm Hash digest
SHA256 f423c059087435fcc7f61295943e830fcdaf9682493306ea4a57fb83a2719929
MD5 dff2069ab8fd9f46123d87ee6a5935ef
BLAKE2b-256 6cd48ad3d575e6dc3cb13ecde1227c713baaf0a325cdd7d2803709964b5ebdfd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page