AlbughdadiM / satellite-sam-dashboard

This app integrates the Segment Anything Model (SAM) with Sentinel-2 data. The app is built using Dash Plotly and dash leaflet. It allows segmenting satellite images using the two ways provided by SAM: automatic mask generator and prompt segmentation (using points and bounding boxes).
MIT License
27 stars 5 forks source link

Low Resolution Map Tiles #5

Open sharoseali opened 2 weeks ago

sharoseali commented 2 weeks ago

HI @AlbughdadiM Thanks for your great work. I was just curious about more zoomed tiles. Zooming it more is not load tile details. Is there any option I can upload my on-tiff imagery? I also have my wms link. I edit in the config file but it isn't loading any tiles.

Any help for this? Thanks

AlbughdadiM commented 2 weeks ago

Hi @sharoseali. On my side I can zoom in/out without any troubles. If tiles are not loading, it can be related to connection problems. Of course you can use the WMS layer of your choice. In a similar project, I used a very high resolution WMS layer and a SAR WMS layer. It is also possible to upload a GeoTiff image. I have done this in another project. I will try to update this repository with the option of uploading a GeoTiff when I have sometime. If you are interested in the pieces of code that allow you to upload your GeoTiff, I can share that with you, with indications where to place them in the code. But unfortunately I will not have time to do end-to-end test on this project for the next month.

sharoseali commented 2 weeks ago

hi @AlbughdadiM Thanks for answering. On my side wms link written inside the code isn't loading so many deep detailed tiles. I think the connection is fine. I will check one time more. However, I have my own link as well. I have also opened the link in QGIS as well. But I am not sure how to pick the correct information of layers from QGIS and paste here in the code:


wms_layer = dl.WMSTileLayer(
    url="https://tiles.maps.eox.at/wms",
    layers="s2cloudless-2020_3857",
    format="image/jpeg",
    transparent=True,
    tileSize=512,  # Try a higher tile size for better resolution
    minZoom=0,
    maxZoom=25,  # Increase max zoom level for deeper zoom
    attribution="Sentinel-2 cloudless layer for 2020 by EOX",
)

Also, of course, you can share the code for opening the GeoTIFF tile. I will try it on my side. I will be so glad. thanks again for your nice work and response.

AlbughdadiM commented 2 weeks ago

@sharoseali I want to also mention that some providers limit the tile size and max zoom to avoid downloading large and detailed areas from WMS tiles. You can test yourself with the original WMS layer and a large ROI that the image download won't work.

AlbughdadiM commented 2 weeks ago

Regarding the upload option:

upload_data_card = dbc.Card(
    [
        dbc.CardHeader(html.H2("Data upload")),
        dbc.CardBody(
            [
                dcc.Upload(
                    id="upload-tif",
                    children=html.Div(["Drag and Drop or ", html.A("Select Files")]),
                    style={
                        "width": "100%",
                        "height": "60px",
                        "lineHeight": "60px",
                        "borderWidth": "1px",
                        "borderStyle": "dashed",
                        "borderRadius": "5px",
                        "textAlign": "center",
                        "margin": "10px",
                    },
                    multiple=False,  # Set to True if multiple file upload is needed
                ),
                dcc.Store(id="output-data-upload", data=None),
            ]
        ),
    ]
)
@app.callback(
    [
        Output("output-data-upload", "data"),
        Output("map-card", "children", allow_duplicate=True),
    ],
    [
        Input("upload-tif", "contents"),
        Input("upload-tif", "filename"),
    ],
    prevent_initial_call="initial_duplicate",
)
def update_output_upload(contents, filename):
    logging.info(filename)
    if contents is None and filename is None:
        raise PreventUpdate
    _, content_string = contents.split(",")
    process_id = id_generator()
    process_path = os.path.join(WORK_DIR, process_id)  # type: ignore
    os.makedirs(process_path)
    decoded = base64.b64decode(content_string)
    file_path = os.path.join(process_path, filename)
    with open(file_path, "wb") as f:
        f.write(decoded)
    reproj_file_path = os.path.join(
        process_path, filename.split(".")[0] + "_reproj.tif"
    )
    reproj_bounds, img_byte_arr, png_path = reproject_to_epsg4326(
        file_path, reproj_file_path
    )
    leaflet_bounds = [
        [reproj_bounds[1], reproj_bounds[0]],
        [reproj_bounds[3], reproj_bounds[2]],
    ]

    map_center = [
        (leaflet_bounds[0][0] + leaflet_bounds[1][0]) / 2,
        (leaflet_bounds[0][1] + leaflet_bounds[1][1]) / 2,
    ]
    encoded_img = base64.b64encode(img_byte_arr).decode("ascii")
    encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
    print("also here")
    list_children = dl.Map(
        [
            dl.LayersControl(
                [
                    dl.Overlay(dl.TileLayer(), name="OSM", checked=True),
                    dl.Overlay(wms_layer, name="Sentinel-2", checked=True),
                    dl.Overlay(esri_tile_layer, name="ESRI", checked=True),
                ],
                id="layers-control",
                collapsed=True,
            ),
            dl.ImageOverlay(opacity=1, url=encoded_img, bounds=leaflet_bounds),
            dl.FeatureGroup(
                [
                    dl.LocateControl(
                        options={"locateOptions": {"enableHighAccuracy": True}}
                    ),
                    dl.MeasureControl(
                        position="topleft",
                        primaryLengthUnit="kilometers",
                        primaryAreaUnit="sqmeters",
                        id="measure_control",
                    ),
                    dl.EditControl(
                        id="edit_control",
                        draw={
                            "polyline": False,
                            "polygon": False,
                            "circle": False,
                            "circlemarker": False,
                        },
                    ),
                ]
            ),
        ],
        id="map",
        style={
            "width": "100%",
            "height": "80vh",
            "margin": "auto",
            "display": "block",
        },
        center=map_center,
        zoom=12,
        bounds=leaflet_bounds,
    )
    return [[reproj_file_path, png_path, leaflet_bounds]], list_children
@app.callback(
    [
        Output("downloaded_image_path", "data"),
        Output("segment-button", "disabled"),
        Output("map-card", "children", allow_duplicate=True),
        Output("download-button", "disabled"),
    ],
    [
        Input("annotations-table", "data"),
        Input("segment-button", "n_clicks"),
        Input("sam-model", "value"),
        Input("pred-iou-thresh", "value"),
        Input("stability-score-thresh", "value"),
        Input("output-data-upload", "data"),
    ],
    prevent_initial_call="initial_duplicate",
)
def run_segmentation(
    table_data,
    n_clicks,
    sam_model,
    pred_iou_thresh,
    stability_score_thresh,
    input_data,
):
    if n_clicks == 0 or table_data is None:
        raise PreventUpdate
    if n_clicks == 1 and table_data is not None:
        roi = [row for row in table_data if row["type"] == "ROI BBox"][0]
        if input_data:
            image_bounds = input_data[0][2]
            print(input_data)
            tmp_img_path = input_data[0][0]
            roi_bbox = [
                float(image_bounds[0][1]),  # x_min (longitude of SW)
                float(image_bounds[0][0]),  # y_min (latitude of SW)
                float(image_bounds[1][1]),  # x_max (longitude of NE)
                float(image_bounds[1][0]),  # y_max (latitude of NE)
            ]
            map_center = [
                (image_bounds[0][0] + image_bounds[1][0]) / 2,
                (image_bounds[0][1] + image_bounds[1][1]) / 2,
            ]
        else:
            roi_bbox = [
                float(roi["x_min"]),
                float(roi["y_min"]),
                float(roi["x_max"]),
                float(roi["y_max"]),
            ]
            image_bounds = [[roi_bbox[1], roi_bbox[0]], [roi_bbox[3], roi_bbox[2]]]
            map_center = [
                (float(roi["y_min"]) + float(roi["y_max"])) / 2.0,
                (float(roi["x_min"]) + float(roi["x_max"])) / 2.0,
            ]
            tmp_img_path = download_from_wms(
                WMS_URL, roi_bbox, LAYER, IMAGE_FORMAT, WORK_DIR, RESOLUTION  # type: ignore
            )
        types = [row["type"] for row in table_data]
        unique_types = list(set(types))
        if len(table_data) == 2 and unique_types == ["ROI BBox"]:
            segmetnation_path, png_path = generate_automatic_mask(
                tmp_img_path, sam_model, pred_iou_thresh, stability_score_thresh
            )

        else:
            bboxes_geo, foreground_points, background_points, stacked_points, labels = (
                None,
                None,
                None,
                None,
                None,
            )
            geom_df = pd.DataFrame(table_data)
            unique_types = list(set(unique_types) - set("ROI BBox"))
            for u_t in unique_types:
                tmp_df = geom_df.loc[geom_df["type"] == u_t]
                if u_t == "Object BBox":
                    bboxes_geo = tmp_df[columns[2:]].astype(float).values
                if u_t == "Foreground Point":
                    foreground_points = tmp_df[columns[2:4]].astype(float).values
                if u_t == "Background Point":
                    background_points = tmp_df[columns[2:4]].astype(float).values
            if foreground_points is None and background_points is not None:
                stacked_points = np.copy(background_points)
                labels = np.zeros((stacked_points.shape[0]), dtype=np.uint8)
            elif background_points is None and foreground_points is not None:
                stacked_points = np.copy(foreground_points)
                labels = np.ones((stacked_points.shape[0]), dtype=np.uint8)
            elif background_points is not None and foreground_points is not None:
                stacked_points = np.vstack((background_points, foreground_points))
                labels = np.zeros((stacked_points.shape[0]), dtype=np.uint8)
                labels[background_points.shape[0] :] = 1

            segmetnation_path, png_path = sam_prompt_bbox(
                tmp_img_path, bboxes_geo, stacked_points, labels, sam_model, roi_bbox
            )
        encoded_img = base64.b64encode(open(png_path, "rb").read()).decode("ascii")
        encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
        list_children_items = [
            dl.LayersControl(
                [
                    dl.Overlay(dl.TileLayer(), name="OSM", checked=True),
                    dl.Overlay(wms_layer, name="Sentinel-2", checked=True),
                    dl.Overlay(esri_tile_layer, name="ESRI", checked=True),
                ],
                id="layers-control",
                collapsed=True,
            ),
            dl.ImageOverlay(opacity=0.5, url=encoded_img, bounds=image_bounds),
            dl.FeatureGroup(
                [
                    dl.LocateControl(
                        options={"locateOptions": {"enableHighAccuracy": True}}
                    ),
                    dl.MeasureControl(
                        position="topleft",
                        primaryLengthUnit="kilometers",
                        primaryAreaUnit="sqmeters",
                        id="measure_control",
                    ),
                    dl.EditControl(
                        id="edit_control",
                        draw={
                            "polyline": False,
                            "polygon": False,
                            "circle": False,
                            "circlemarker": False,
                        },
                    ),
                ]
            ),
        ]
        if input_data:
            encoded_img = base64.b64encode(open(input_data[0][1], "rb").read()).decode(
                "ascii"
            )
            encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
            list_children_items.insert(
                0, dl.ImageOverlay(opacity=0.5, url=encoded_img, bounds=image_bounds)
            )

        list_children = dl.Map(
            list_children_items,
            id="map",
            style={
                "width": "100%",
                "height": "80vh",
                "margin": "auto",
                "display": "block",
            },
            center=map_center,
            zoom=15,
            bounds=image_bounds,
        )
        return [tmp_img_path, segmetnation_path, png_path], True, list_children, False

There might be slight changes in variables or new ones but this is the general logic and it shouldn't take a lot of changes to make it work. Don't forget to use dash==2.18.2 to use prevent_initial_callback and use allow_duplicate=True, which allows multiple callbacks to update the same output. Please let me know if this works for you or in case you have other questions.

AlbughdadiM commented 2 weeks ago

you would also need this

def reproject_to_epsg4326(input_path, output_path):
    with rasterio.open(input_path) as src:
        dst_crs = "EPSG:4326"
        transform, width, height = calculate_default_transform(
            src.crs, dst_crs, src.width, src.height, *src.bounds
        )
        dst_meta = src.meta.copy()
        dst_meta.update(
            {"crs": dst_crs, "transform": transform, "width": width, "height": height}
        )
        reprojected_bounds = transform_bounds(src.crs, dst_crs, *src.bounds)
        with rasterio.open(output_path, "w", **dst_meta) as dst:
            for i in range(1, src.count + 1):
                reproject(
                    source=rasterio.band(src, i),
                    destination=rasterio.band(dst, i),
                    src_transform=src.transform,
                    src_crs=src.crs,
                    dst_transform=transform,
                    dst_crs=dst_crs,
                    resampling=Resampling.nearest,
                )

        reprojected_image = tiff.imread(output_path)
        # reprojected_image = (
        #     (reprojected_image - reprojected_image.min())
        #     / (reprojected_image.max() - reprojected_image.min())
        #     * 255
        # )
        reprojected_image = reprojected_image.astype(np.uint8)

        # Create a PIL image from the array
        png_image = Image.fromarray(reprojected_image)
        img_byte_arr = io.BytesIO()
        png_image.save(img_byte_arr, format="PNG")
        img_byte_arr.seek(0)  # Go to the start
        png_file_path = output_path.split(".")[0] + ".png"
        with open(png_file_path, "wb") as f:
            f.write(img_byte_arr.getbuffer())
    return reprojected_bounds, img_byte_arr.getvalue(), png_file_path
sharoseali commented 2 weeks ago

Hi @AlbughdadiM, in your provided code for uploading TIFF images, will esri_tile_layer will be fine? As it's not

 # ESRI base map options
    esri_tile_layer = dl.TileLayer(
    url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
    attribution="© Esri — Esri, DeLorme, NAVTEQ",)

and what is process_id here id_generator() function is missing. Is it some random ID generator like UUID ?

 def update_output_upload(contents, filename):
    logging.info(filename)
    if contents is None and filename is None:
        raise PreventUpdate
    _, content_string = contents.split(",")
    process_id = id_generator()
AlbughdadiM commented 2 weeks ago

Esri layer is another wmts I am using but it is not necessary. The ID generator generates a unique ID for each task to avoid confusion of data in case of multiple requests with same image name at the same time.

AlbughdadiM commented 2 weeks ago

This is the ESRI WMTS layer

esri_tile_layer = dl.TileLayer(
    url="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
    attribution="Esri",
)

and the ID generator

import uuid
def id_generator():
    return str(uuid.uuid4())
sharoseali commented 2 weeks ago

And some undefined things are also there:

image

Will be easy if you provide correct imports and missing definitions

AlbughdadiM commented 2 weeks ago
import io
import os
from io import BytesIO
from typing import Tuple
import uuid
import tifffile as tiff
from owslib.wms import WebMapService
from pyproj import Transformer, CRS
import rasterio
from rasterio.warp import (
    calculate_default_transform,
    reproject,
    Resampling,
    transform_bounds,
)
import numpy as np
from PIL import Image

This should cover the missing imports, and extra imports I am using in my code

sharoseali commented 2 weeks ago

Thanks for providing all the details. Once it starts running, I will share the whole working code here with the uploading files feature. Meanwhile, I think we have to update something else as well. I am getting some errors on the interface relating to ID : image

AlbughdadiM commented 2 weeks ago

Regarding the upload option:

  • Place this in the layout of the app.py
upload_data_card = dbc.Card(
    [
        dbc.CardHeader(html.H2("Data upload")),
        dbc.CardBody(
            [
                dcc.Upload(
                    id="upload-tif",
                    children=html.Div(["Drag and Drop or ", html.A("Select Files")]),
                    style={
                        "width": "100%",
                        "height": "60px",
                        "lineHeight": "60px",
                        "borderWidth": "1px",
                        "borderStyle": "dashed",
                        "borderRadius": "5px",
                        "textAlign": "center",
                        "margin": "10px",
                    },
                    multiple=False,  # Set to True if multiple file upload is needed
                ),
                dcc.Store(id="output-data-upload", data=None),
            ]
        ),
    ]
)
  • You will also need a callback to save the uploaded image in a temp file in the container and overlay it on the map
@app.callback(
    [
        Output("output-data-upload", "data"),
        Output("map-card", "children", allow_duplicate=True),
    ],
    [
        Input("upload-tif", "contents"),
        Input("upload-tif", "filename"),
    ],
    prevent_initial_call="initial_duplicate",
)
def update_output_upload(contents, filename):
    logging.info(filename)
    if contents is None and filename is None:
        raise PreventUpdate
    _, content_string = contents.split(",")
    process_id = id_generator()
    process_path = os.path.join(WORK_DIR, process_id)  # type: ignore
    os.makedirs(process_path)
    decoded = base64.b64decode(content_string)
    file_path = os.path.join(process_path, filename)
    with open(file_path, "wb") as f:
        f.write(decoded)
    reproj_file_path = os.path.join(
        process_path, filename.split(".")[0] + "_reproj.tif"
    )
    reproj_bounds, img_byte_arr, png_path = reproject_to_epsg4326(
        file_path, reproj_file_path
    )
    leaflet_bounds = [
        [reproj_bounds[1], reproj_bounds[0]],
        [reproj_bounds[3], reproj_bounds[2]],
    ]

    map_center = [
        (leaflet_bounds[0][0] + leaflet_bounds[1][0]) / 2,
        (leaflet_bounds[0][1] + leaflet_bounds[1][1]) / 2,
    ]
    encoded_img = base64.b64encode(img_byte_arr).decode("ascii")
    encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
    print("also here")
    list_children = dl.Map(
        [
            dl.LayersControl(
                [
                    dl.Overlay(dl.TileLayer(), name="OSM", checked=True),
                    dl.Overlay(wms_layer, name="Sentinel-2", checked=True),
                    dl.Overlay(esri_tile_layer, name="ESRI", checked=True),
                ],
                id="layers-control",
                collapsed=True,
            ),
            dl.ImageOverlay(opacity=1, url=encoded_img, bounds=leaflet_bounds),
            dl.FeatureGroup(
                [
                    dl.LocateControl(
                        options={"locateOptions": {"enableHighAccuracy": True}}
                    ),
                    dl.MeasureControl(
                        position="topleft",
                        primaryLengthUnit="kilometers",
                        primaryAreaUnit="sqmeters",
                        id="measure_control",
                    ),
                    dl.EditControl(
                        id="edit_control",
                        draw={
                            "polyline": False,
                            "polygon": False,
                            "circle": False,
                            "circlemarker": False,
                        },
                    ),
                ]
            ),
        ],
        id="map",
        style={
            "width": "100%",
            "height": "80vh",
            "margin": "auto",
            "display": "block",
        },
        center=map_center,
        zoom=12,
        bounds=leaflet_bounds,
    )
    return [[reproj_file_path, png_path, leaflet_bounds]], list_children
  • Update callback that does image download/or use the uploaded image and do the segmentation and shows result on the map
@app.callback(
    [
        Output("downloaded_image_path", "data"),
        Output("segment-button", "disabled"),
        Output("map-card", "children", allow_duplicate=True),
        Output("download-button", "disabled"),
    ],
    [
        Input("annotations-table", "data"),
        Input("segment-button", "n_clicks"),
        Input("sam-model", "value"),
        Input("pred-iou-thresh", "value"),
        Input("stability-score-thresh", "value"),
        Input("output-data-upload", "data"),
    ],
    prevent_initial_call="initial_duplicate",
)
def run_segmentation(
    table_data,
    n_clicks,
    sam_model,
    pred_iou_thresh,
    stability_score_thresh,
    input_data,
):
    if n_clicks == 0 or table_data is None:
        raise PreventUpdate
    if n_clicks == 1 and table_data is not None:
        roi = [row for row in table_data if row["type"] == "ROI BBox"][0]
        if input_data:
            image_bounds = input_data[0][2]
            print(input_data)
            tmp_img_path = input_data[0][0]
            roi_bbox = [
                float(image_bounds[0][1]),  # x_min (longitude of SW)
                float(image_bounds[0][0]),  # y_min (latitude of SW)
                float(image_bounds[1][1]),  # x_max (longitude of NE)
                float(image_bounds[1][0]),  # y_max (latitude of NE)
            ]
            map_center = [
                (image_bounds[0][0] + image_bounds[1][0]) / 2,
                (image_bounds[0][1] + image_bounds[1][1]) / 2,
            ]
        else:
            roi_bbox = [
                float(roi["x_min"]),
                float(roi["y_min"]),
                float(roi["x_max"]),
                float(roi["y_max"]),
            ]
            image_bounds = [[roi_bbox[1], roi_bbox[0]], [roi_bbox[3], roi_bbox[2]]]
            map_center = [
                (float(roi["y_min"]) + float(roi["y_max"])) / 2.0,
                (float(roi["x_min"]) + float(roi["x_max"])) / 2.0,
            ]
            tmp_img_path = download_from_wms(
                WMS_URL, roi_bbox, LAYER, IMAGE_FORMAT, WORK_DIR, RESOLUTION  # type: ignore
            )
        types = [row["type"] for row in table_data]
        unique_types = list(set(types))
        if len(table_data) == 2 and unique_types == ["ROI BBox"]:
            segmetnation_path, png_path = generate_automatic_mask(
                tmp_img_path, sam_model, pred_iou_thresh, stability_score_thresh
            )

        else:
            bboxes_geo, foreground_points, background_points, stacked_points, labels = (
                None,
                None,
                None,
                None,
                None,
            )
            geom_df = pd.DataFrame(table_data)
            unique_types = list(set(unique_types) - set("ROI BBox"))
            for u_t in unique_types:
                tmp_df = geom_df.loc[geom_df["type"] == u_t]
                if u_t == "Object BBox":
                    bboxes_geo = tmp_df[columns[2:]].astype(float).values
                if u_t == "Foreground Point":
                    foreground_points = tmp_df[columns[2:4]].astype(float).values
                if u_t == "Background Point":
                    background_points = tmp_df[columns[2:4]].astype(float).values
            if foreground_points is None and background_points is not None:
                stacked_points = np.copy(background_points)
                labels = np.zeros((stacked_points.shape[0]), dtype=np.uint8)
            elif background_points is None and foreground_points is not None:
                stacked_points = np.copy(foreground_points)
                labels = np.ones((stacked_points.shape[0]), dtype=np.uint8)
            elif background_points is not None and foreground_points is not None:
                stacked_points = np.vstack((background_points, foreground_points))
                labels = np.zeros((stacked_points.shape[0]), dtype=np.uint8)
                labels[background_points.shape[0] :] = 1

            segmetnation_path, png_path = sam_prompt_bbox(
                tmp_img_path, bboxes_geo, stacked_points, labels, sam_model, roi_bbox
            )
        encoded_img = base64.b64encode(open(png_path, "rb").read()).decode("ascii")
        encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
        list_children_items = [
            dl.LayersControl(
                [
                    dl.Overlay(dl.TileLayer(), name="OSM", checked=True),
                    dl.Overlay(wms_layer, name="Sentinel-2", checked=True),
                    dl.Overlay(esri_tile_layer, name="ESRI", checked=True),
                ],
                id="layers-control",
                collapsed=True,
            ),
            dl.ImageOverlay(opacity=0.5, url=encoded_img, bounds=image_bounds),
            dl.FeatureGroup(
                [
                    dl.LocateControl(
                        options={"locateOptions": {"enableHighAccuracy": True}}
                    ),
                    dl.MeasureControl(
                        position="topleft",
                        primaryLengthUnit="kilometers",
                        primaryAreaUnit="sqmeters",
                        id="measure_control",
                    ),
                    dl.EditControl(
                        id="edit_control",
                        draw={
                            "polyline": False,
                            "polygon": False,
                            "circle": False,
                            "circlemarker": False,
                        },
                    ),
                ]
            ),
        ]
        if input_data:
            encoded_img = base64.b64encode(open(input_data[0][1], "rb").read()).decode(
                "ascii"
            )
            encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
            list_children_items.insert(
                0, dl.ImageOverlay(opacity=0.5, url=encoded_img, bounds=image_bounds)
            )

        list_children = dl.Map(
            list_children_items,
            id="map",
            style={
                "width": "100%",
                "height": "80vh",
                "margin": "auto",
                "display": "block",
            },
            center=map_center,
            zoom=15,
            bounds=image_bounds,
        )
        return [tmp_img_path, segmetnation_path, png_path], True, list_children, False

There might be slight changes in variables or new ones but this is the general logic and it shouldn't take a lot of changes to make it work. Don't forget to use dash==2.18.2 to use prevent_initial_callback and use allow_duplicate=True, which allows multiple callbacks to update the same output. Please let me know if this works for you or in case you have other questions.

The first piece of code contains the layout updates that should be implemented to make the upload component work

AlbughdadiM commented 2 weeks ago

you finally add these components to the layout after declaring them as follows

app.layout = html.Div(
    [
        dbc.Spinner(
            id="loading-1",
            type="grow",
            color="success",
            children=[
                dcc.Store(id="downloaded_image_path"),
                dcc.Store(id="prev-table-data"),
                navbar,
                dbc.Container(
                    [
                        dbc.Row(
                            [
                                dbc.Col(
                                    image_annotation_card,
                                    md=7,
                                ),
                                dbc.Col(
                                    dbc.Stack([upload_data_card,annotated_data_card, model_card]), # This where upload_data_card is added to the layout
                                    md=5,
                                ),
                            ],
                        ),
                    ],
                    fluid=True,
                ),
            ],
        )
    ]
)
sharoseali commented 2 weeks ago

Yeah, @AlbughdadiM I have already written layout updates and e upload component work. However, the app layout wasn't updated. But as soon as the page uploads it gives an exception of an invalid path. Seems it started to process image running even without uploading file-path from user.

This is the CopyPaste-friendlyy version of the traceback.
Traceback (most recent call last):
  File "/home/test/projects/segment-geospatial/TKDM/satellite-sam-dashboard/src/app.py", line 414, in reproject_to_epsg4326
    with rasterio.open(input_path) as src:
  File "/home/test/anaconda3/envs/geo/lib/python3.9/site-packages/rasterio/env.py", line 451, in wrapper
    return f(*args, **kwds)
  File "/home/test/anaconda3/envs/geo/lib/python3.9/site-packages/rasterio/__init__.py", line 216, in open
    raise TypeError("invalid path or file: {0!r}".format(fp))
TypeError: invalid path or file: None

Here is the new app.py. Will be so helpful If you review this to make it work . Thanks again for your continuous suppport

app.py

AlbughdadiM commented 2 weeks ago

Yeah, @AlbughdadiM I have already written layout updates and e upload component work. However, the app layout wasn't updated. But as soon as the page uploads it gives an exception of an invalid path. Seems it started to process image running even without uploading file-path from user.

This is the CopyPaste-friendlyy version of the traceback.
Traceback (most recent call last):
  File "/home/test/projects/segment-geospatial/TKDM/satellite-sam-dashboard/src/app.py", line 414, in reproject_to_epsg4326
    with rasterio.open(input_path) as src:
  File "/home/test/anaconda3/envs/geo/lib/python3.9/site-packages/rasterio/env.py", line 451, in wrapper
    return f(*args, **kwds)
  File "/home/test/anaconda3/envs/geo/lib/python3.9/site-packages/rasterio/__init__.py", line 216, in open
    raise TypeError("invalid path or file: {0!r}".format(fp))
TypeError: invalid path or file: None

Here is the new app.py. Will be so helpful If you review this to make it work . Thanks again for your continuous suppport

app.py

I think you changed the callback function to upload data, you used the auxiliary function reproject_to_espog4326 instead. The reprojection function should be a normal function that can be placed in utils.py call it from the callback function i mentioned above and I will put also here for convince

@app.callback(
    [
        Output("output-data-upload", "data"),
        Output("map-card", "children", allow_duplicate=True),
    ],
    [
        Input("upload-tif", "contents"),
        Input("upload-tif", "filename"),
    ],
    prevent_initial_call="initial_duplicate",
)
def update_output_upload(contents, filename):
    logging.info(filename)
    if contents is None and filename is None:
        raise PreventUpdate
    _, content_string = contents.split(",")
    process_id = id_generator()
    process_path = os.path.join(WORK_DIR, process_id)  # type: ignore
    os.makedirs(process_path)
    decoded = base64.b64decode(content_string)
    file_path = os.path.join(process_path, filename)
    with open(file_path, "wb") as f:
        f.write(decoded)
    reproj_file_path = os.path.join(
        process_path, filename.split(".")[0] + "_reproj.tif"
    )
    reproj_bounds, img_byte_arr, png_path = reproject_to_epsg4326(
        file_path, reproj_file_path
    )
    leaflet_bounds = [
        [reproj_bounds[1], reproj_bounds[0]],
        [reproj_bounds[3], reproj_bounds[2]],
    ]

    map_center = [
        (leaflet_bounds[0][0] + leaflet_bounds[1][0]) / 2,
        (leaflet_bounds[0][1] + leaflet_bounds[1][1]) / 2,
    ]
    encoded_img = base64.b64encode(img_byte_arr).decode("ascii")
    encoded_img = "{}{}".format("data:image/png;base64, ", encoded_img)
    print("also here")
    list_children = dl.Map(
        [
            dl.LayersControl(
                [
                    dl.Overlay(dl.TileLayer(), name="OSM", checked=True),
                    dl.Overlay(wms_layer, name="Sentinel-2", checked=True),
                    dl.Overlay(esri_tile_layer, name="ESRI", checked=True),
                ],
                id="layers-control",
                collapsed=True,
            ),
            dl.ImageOverlay(opacity=1, url=encoded_img, bounds=leaflet_bounds),
            dl.FeatureGroup(
                [
                    dl.LocateControl(
                        options={"locateOptions": {"enableHighAccuracy": True}}
                    ),
                    dl.MeasureControl(
                        position="topleft",
                        primaryLengthUnit="kilometers",
                        primaryAreaUnit="sqmeters",
                        id="measure_control",
                    ),
                    dl.EditControl(
                        id="edit_control",
                        draw={
                            "polyline": False,
                            "polygon": False,
                            "circle": False,
                            "circlemarker": False,
                        },
                    ),
                ]
            ),
        ],
        id="map",
        style={
            "width": "100%",
            "height": "80vh",
            "margin": "auto",
            "display": "block",
        },
        center=map_center,
        zoom=12,
        bounds=leaflet_bounds,
    )
    return [[reproj_file_path, png_path, leaflet_bounds]], list_children