Skip to content

rivabar.core

rivabar.core

map_river_banks(fname, dirname, start_x, start_y, end_x, end_y, file_type, ch_belt_smooth_factor=1000000000.0, remove_smaller_components=True, delete_pixels_polys=False, ch_belt_half_width=2000, water_index_type='mndwi', mndwi_threshold=0.01, small_hole_threshold=64, plot_D_primal=False, min_g_primal_length=100000, solidity_filter=True, radius=50, min_main_path_length=2000, flip_outlier_edges=False, check_edges=False, filter_contours=False, min_tributary_length=100)

Map channel centerlines and banks from a georeferenced image.

This function has been refactored into multiple phases for better maintainability.

Source code in rivabar/core.py
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
def map_river_banks(fname, dirname, start_x, start_y, end_x, end_y, file_type,
    ch_belt_smooth_factor=1e9, remove_smaller_components=True, delete_pixels_polys=False,
    ch_belt_half_width=2000, water_index_type='mndwi', mndwi_threshold=0.01, small_hole_threshold=64, plot_D_primal=False,
    min_g_primal_length=100000, solidity_filter=True, radius=50, min_main_path_length=2000,
    flip_outlier_edges=False, check_edges=False, filter_contours=False,
    min_tributary_length=100):
    """
    Map channel centerlines and banks from a georeferenced image.

    This function has been refactored into multiple phases for better maintainability.
    """

    # Phase 1: Initial setup and skeletonization
    result = _initial_skeletonization_and_graph_setup(
        fname, dirname, start_x, start_y, end_x, end_y, file_type,
        water_index_type, mndwi_threshold, delete_pixels_polys, small_hole_threshold,
        remove_smaller_components, solidity_filter
    )

    if result[0] is None:
        return None, None, None, None, None, None, None, None, None, None, None

    graph, start_ind, end_ind, mndwi, dataset, left_utm_x, upper_utm_y, right_utm_x, lower_utm_y, delta_x, delta_y = result
    if dataset is None:
        print('dataset is not defined!')

    # Phase 2: Find main path with fallback logic
    graph, path, start_ind, end_ind = _find_main_path_with_fallbacks(
        graph, start_x, start_y, end_x, end_y, 
        start_ind, end_ind, mndwi, left_utm_x, upper_utm_y, delta_x, delta_y
    )
    if path is None:
        return None, None, None, None, None, None, None, None, None, None, None

    # Phase 3: Process graph and extend edges
    G = _process_graph_and_extend_edges(graph, path, start_ind, end_ind, radius,
                                        left_utm_x=left_utm_x, upper_utm_y=upper_utm_y,
                                        delta_x=delta_x, delta_y=delta_y,
                                        min_tributary_length=min_tributary_length)

    # Phase 4: Extract and smooth main path
    path_result = _extract_and_smooth_main_path(G, start_ind, end_ind, min_main_path_length)
    if path_result[0] is None:
        return None, None, None, None, None, None, None, None, None, None, None

    xcoords, ycoords, xcoords_sm, ycoords_sm, main_path = path_result

    # Phase 5: Polygonize centerline network
    gdf = _polygonize_and_process_centerlines(G, main_path)

    # Phase 6: Create channel belt and boundaries
    xs, ys, poly1, poly2, xcoords1, xcoords2, ycoords1, ycoords2 = _create_channel_belt_and_boundaries(
        xcoords, ycoords, xcoords_sm, ycoords_sm, ch_belt_smooth_factor, ch_belt_half_width, gdf
    )

    # Phase 7: Create UTM geodataframe
    gdf2, poly1_utm, poly2_utm = _create_UTM_geodataframe(gdf, poly1, poly2, left_utm_x, upper_utm_y, delta_x, delta_y, dataset, mndwi)

    # Phase 8: Create primal graph
    G_primal, primal_start_ind, primal_end_ind = _create_primal_graph(gdf2, xcoords1, ycoords1, xcoords2, ycoords2, left_utm_x, 
                         upper_utm_y, delta_x, delta_y, min_g_primal_length)
    if G_primal is None:
        return None, None, None, None, None, None, None, None, None, None, None

    # Phase 9: Create rook graph
    G_rook = _create_rook_graph(gdf2, poly1_utm, poly2_utm, mndwi, dataset, ch_belt_half_width, delta_x, filter_contours=filter_contours)

    if G_rook is None:
        return None, None, None, None, None, None, None, None, None, None, None

    smooth_banklines(G_rook, dataset, mndwi, save_smooth_lines=True)

    # Phase 10: Set half channel widths
    print('Phase 10: Set half channel widths')
    set_half_channel_widths(G_primal, G_rook, dataset, mndwi)

    # Phase 11: Create directed graph
    print('Phase 11: Create directed graph')
    xs, ys = convert_to_utm(np.array(xs), np.array(ys), left_utm_x, upper_utm_y, delta_x, delta_y)
    # sometimes xs and ys need to be flipped (I have no idea why):
    start_point_dist = np.sqrt((xs[0]-start_x)**2 + (ys[0]-start_y)**2)
    end_point_dist = np.sqrt((xs[0]-end_x)**2 + (ys[0]-end_y)**2)
    if end_point_dist < start_point_dist:
        xs = xs[::-1]; ys = ys[::-1]
    D_primal, source_nodes, sink_nodes = create_directed_multigraph(G_primal, G_rook, xs, ys, primal_start_ind, primal_end_ind, 
                                                                    flip_outlier_edges=flip_outlier_edges, check_edges=check_edges)

    # Phase 12: Get bank coordinates for main channel
    print('Phase 12: Get bank coordinates for main channel')
    start_node, inds = find_start_node(D_primal)
    if start_node is not None:
        edge_path = traverse_multigraph(D_primal, start_node)
        D_primal.graph['main_path'] = edge_path # store main path as a graph attribute
        x, y, x_utm1, y_utm1, x_utm2, y_utm2 = get_bank_coords_for_main_channel(D_primal, mndwi, edge_path, dataset)
        D_primal.graph['main_channel_cl_coords'] = np.vstack((x, y)).T
        D_primal.graph['main_channel_bank1_coords'] = np.vstack((x_utm1, y_utm1)).T
        D_primal.graph['main_channel_bank2_coords'] = np.vstack((x_utm2, y_utm2)).T

    # Carry tributary confluence information from skeleton graph to D_primal
    D_primal.graph['tributary_confluences'] = G.graph.get('tributary_confluences', [])

    # Set graph names
    D_primal.name = fname
    G_rook.name = fname
    G_primal.name = fname

    if plot_D_primal:
        fig, ax = plot_im_and_lines(mndwi, left_utm_x, right_utm_x, lower_utm_y, upper_utm_y,
            G_rook, G_primal, D_primal, smoothing=False, start_x=start_x, start_y=start_y, end_x=end_x,
            end_y=end_y, plot_lines=False)
        plot_graph_w_colors(D_primal, ax)

    return D_primal, G_rook, G_primal, mndwi, dataset, left_utm_x, right_utm_x, lower_utm_y, upper_utm_y, xs, ys

extract_centerline(*args, **kwargs)

Backward-compatible alias for :func:map_river_banks.

The original implementation (rivabar_legacy.py) was removed; this alias accepts the same parameters and returns the same 11-tuple (D_primal, G_rook, G_primal, mndwi, dataset, left_utm_x, right_utm_x, lower_utm_y, upper_utm_y, xs, ys). Note that the default for remove_smaller_components is True here (the legacy default was False). New code should use map_river_banks or the River class.

Source code in rivabar/core.py
822
823
824
825
826
827
828
829
830
831
832
833
def extract_centerline(*args, **kwargs):
    """
    Backward-compatible alias for :func:`map_river_banks`.

    The original implementation (rivabar_legacy.py) was removed; this alias
    accepts the same parameters and returns the same 11-tuple
    ``(D_primal, G_rook, G_primal, mndwi, dataset, left_utm_x, right_utm_x,
    lower_utm_y, upper_utm_y, xs, ys)``. Note that the default for
    ``remove_smaller_components`` is True here (the legacy default was False).
    New code should use ``map_river_banks`` or the ``River`` class.
    """
    return map_river_banks(*args, **kwargs)

main(fname, dirname, start_x, start_y, end_x, end_y, file_type, **kwargs)

Main function to extract centerlines from command line arguments.

Source code in rivabar/core.py
836
837
838
839
840
841
842
843
844
845
846
847
def main(fname, dirname, start_x, start_y, end_x, end_y, file_type, **kwargs):
    """Main function to extract centerlines from command line arguments."""
    return map_river_banks(
        fname=fname,
        dirname=dirname, 
        start_x=float(start_x),
        start_y=float(start_y),
        end_x=float(end_x), 
        end_y=float(end_y),
        file_type=file_type,
        **kwargs
    )