def network_analyze( traces: gpd.GeoDataFrame, area: gpd.GeoDataFrame, name: str, coverage_gdf: gpd.GeoDataFrame, circle_radius: float, ) -> Tuple[Network, pd.Series]: """ Analyze traces and area for results. """ network = Network( trace_gdf=traces, area_gdf=area, name=name, determine_branches_nodes=True, snap_threshold=0.001, ) points = network.representative_points() if not len(points) == 1: raise ValueError("Expected one target area representative point.") point = points[0] amount_of_coverage = assess_coverage(target_centroid=point, coverage_gdf=coverage_gdf, radius=circle_radius) describe_df = describe_random_network( network=network, target_centroid=point, name=name, amount_of_coverage=amount_of_coverage, radius=circle_radius, ) return network, describe_df.iloc[0]
def test_network_contour_grid(traces, areas, snap_threshold, name, dataframe_regression): """ Test contour_grid with network determined b and n. """ assert isinstance(name, str) assert isinstance(traces, gpd.GeoDataFrame) assert isinstance(areas, gpd.GeoDataFrame) traces = traces.iloc[0:150] network = Network( trace_gdf=traces, area_gdf=areas, determine_branches_nodes=True, snap_threshold=snap_threshold, truncate_traces=True, circular_target_area=False, name=name, ) sampled_grid = network.contour_grid() fig, ax = network.plot_contour( parameter=Param.FRACTURE_INTENSITY_P21.value.name, sampled_grid=sampled_grid) assert isinstance(fig, Figure) assert isinstance(ax, Axes) plt.close("all") assert isinstance(sampled_grid, gpd.GeoDataFrame) assert sampled_grid.shape[0] > 0 assert isinstance(sampled_grid.geometry.values[0], Polygon) sampled_grid.sort_index(inplace=True) dataframe_regression.check( pd.DataFrame(sampled_grid).drop(columns=["geometry"]))
def save_azimuth_bin_data(network: Network, other_results_path: Path, loc_hash: str): """ Save azimuth bin data from a Network. """ trace_bins, _, _ = network.plot_trace_azimuth() branch_bins, _, _ = network.plot_branch_azimuth() trace_bin_df, branch_bin_df = bins_to_dataframes(trace_bins, branch_bins) utils.save_csv(trace_bin_df, other_results_path / f"trace_{loc_hash}.csv") utils.save_csv(branch_bin_df, other_results_path / f"branch_{loc_hash}.csv")
def test_network_circular_target_area(trace_gdf, area_gdf, name, data_regression): """ Test network circular_target_area. """ network_circular = Network( trace_gdf=trace_gdf, area_gdf=area_gdf, name=name, circular_target_area=True, determine_branches_nodes=True, ) network_non_circular = Network( trace_gdf=trace_gdf, area_gdf=area_gdf, name=name, circular_target_area=False, determine_branches_nodes=False, ) lengths_circular = network_circular.trace_length_array lengths_non_circular = network_non_circular.trace_length_array lengths_circular_sum = np.sum(lengths_circular) lengths_non_circular_sum = np.sum(lengths_non_circular) data_regression.check({ "circular_sum": lengths_circular_sum.item(), "non_circular_sum": lengths_non_circular_sum.item(), }) # test both traces and branches for right boundary_intersect_counts for boundary_intersect_count, gdf, name in zip( ( # network_circular.trace_boundary_intersect_count, # network_circular.branch_boundary_intersect_count, network_circular.trace_data.boundary_intersect_count_desc( label="Trace"), network_circular.branch_data.boundary_intersect_count_desc( label="Branch"), ), (network_circular.trace_gdf, network_circular.branch_gdf), ("Trace", "Branch"), ): assert all(isinstance(val, str) for val in boundary_intersect_count) assert all( isinstance(val, int) for val in boundary_intersect_count.values()) assert sum(boundary_intersect_count.values()) == gdf.shape[0] assert sum(gdf.geometry.intersects( area_gdf.geometry.iloc[0].boundary)) == sum(( boundary_intersect_count[f"{name} Boundary 1 Intersect Count"], boundary_intersect_count[f"{name} Boundary 2 Intersect Count"], ))
def test_plaifiny_rose_plot_manual(): """ Test plainify_rose_plot. """ network = Network( trace_gdf=tests.flato_traces_gdf(), area_gdf=tests.flato_area_gdf(), snap_threshold=0.001, determine_branches_nodes=False, truncate_traces=True, ) _, fig, ax = network.plot_trace_azimuth() network_scripts.plainify_rose_plot(fig, ax)
def test_empty_numerical_desc_manual(): """ Test that all keys are valid. """ network = Network( trace_gdf=tests.flato_traces_gdf(), area_gdf=tests.flato_area_gdf(), snap_threshold=0.001, determine_branches_nodes=True, truncate_traces=True, ) assert all([ key in network_scripts.empty_numerical_desc() for key in network.numerical_network_description() ])
def test_network_topology_reassignment( trace_gdf: gpd.GeoDataFrame, area_gdf: gpd.GeoDataFrame, tmp_path: Path, network_name: str, truncate_traces: bool, circular_target_area: bool, snap_threshold: float, ): """ Test reassignment of Network branch_gdf and node_gdf. """ network_params: Dict[str, Any] = dict( trace_gdf=trace_gdf, area_gdf=area_gdf, name=network_name, truncate_traces=truncate_traces, circular_target_area=circular_target_area, snap_threshold=snap_threshold, ) network = Network(**network_params, determine_branches_nodes=True) original_description = network.numerical_network_description() # Explicitly name outputs branches_name = f"{network_name}_branches.geojson" nodes_name = f"{network_name}_nodes.geojson" # Save branches and nodes to tmp_path directory network.write_branches_and_nodes( output_dir_path=tmp_path, branches_name=branches_name, nodes_name=nodes_name ) branches_path = tmp_path / branches_name nodes_path = tmp_path / nodes_name assert branches_path.exists() assert nodes_path.exists() branches_gdf = read_geofile(branches_path) nodes_gdf = read_geofile(nodes_path) assert isinstance(branches_gdf, gpd.GeoDataFrame) assert isinstance(nodes_gdf, gpd.GeoDataFrame) new_network = Network( **network_params, determine_branches_nodes=False, branch_gdf=branches_gdf, node_gdf=nodes_gdf, ) new_description = new_network.numerical_network_description() original_df = pd.DataFrame([original_description]) new_df = pd.DataFrame([new_description]) assert_frame_equal(original_df, new_df) assert_frame_equal(new_network.branch_gdf, branches_gdf)
def plot_and_save_azimuths(network: Network, other_results_path: Path, name: str): """ Plot azimuth rose plots and save them. """ _, fig, ax = network.plot_trace_azimuth() # save normal version fig.savefig(other_results_path / f"{name}_trace_azimuths.svg", bbox_inches="tight") # Make plain version plainify_rose_plot(fig, ax) # Save plain version fig.savefig( other_results_path / f"{name}_trace_azimuths_plain.svg", bbox_inches="tight", transparent=True, ) # Branch version _, fig, _ = network.plot_branch_azimuth() fig.savefig(other_results_path / f"{name}_branch_azimuths.svg", bbox_inches="tight")
def test_network_kb11_manual(): """ Test Network analysis with KB11 data. Returns the Network. """ trace_gdf = Helpers.kb11_traces area_gdf = Helpers.kb11_area network = Network( trace_gdf=trace_gdf, area_gdf=area_gdf, name="KB11 test", determine_branches_nodes=True, truncate_traces=True, snap_threshold=0.001, circular_target_area=False, ) return network
def random_network_sample(self, determine_branches_nodes=True) -> RandomSample: """ Get random Network sample with a random target area. Returns the network, the sample circle centroid and circle radius. """ # Create random Polygon circle within area target_circle, target_centroid, radius = self.random_target_circle() # Collect into GeoDataFrame and set crs if it exists in input frame area_gdf = gpd.GeoDataFrame({GEOMETRY_COLUMN: [target_circle]}) if self.trace_gdf.crs is not None: area_gdf = area_gdf.set_crs(self.trace_gdf.crs) try: network_maybe: Optional[Network] = Network( trace_gdf=self.trace_gdf, area_gdf=area_gdf, name=self.name, determine_branches_nodes=determine_branches_nodes, snap_threshold=self.snap_threshold, circular_target_area=True, truncate_traces=True, ) except ValueError: logging.error( "Error occurred during creation of random_network_sample.", exc_info=True, extra=dict( sampler_name=self.name, target_centroid=target_centroid, radius=radius, ), ) network_maybe = None return RandomSample( network_maybe=network_maybe, target_centroid=target_centroid, radius=radius, name=self.name, )
def test_azimuth_set_relationships_regression( azimuth_set_ranges: SetRangeTuple, num_regression ): """ Test for azimuth set relationship regression. """ azimuth_set_names: Tuple[str, ...] = ("1", "2", "3")[0 : len(azimuth_set_ranges)] relations_df: pd.DataFrame = Network( Helpers.kb7_traces, # type: ignore Helpers.kb7_area, # type: ignore name="kb7", determine_branches_nodes=True, azimuth_set_ranges=azimuth_set_ranges, azimuth_set_names=azimuth_set_names, snap_threshold=0.001, circular_target_area=False, truncate_traces=True, ).azimuth_set_relationships relations_df_dict = relations_df_to_dict(relations_df) num_regression.check(relations_df_dict)
def test_length_set_relationships_regression(num_regression): """ Test for length set relationship regression. """ trace_length_set_ranges: SetRangeTuple = ( (0, 2), (2, 4), (4, 6), ) trace_length_set_names: Tuple[str, ...] = ("a", "b", "c") relations_df: pd.DataFrame = Network( Helpers.kb7_traces, # type: ignore Helpers.kb7_area, # type: ignore name="kb7", determine_branches_nodes=True, trace_length_set_names=trace_length_set_names, trace_length_set_ranges=trace_length_set_ranges, snap_threshold=0.001, circular_target_area=False, ).azimuth_set_relationships relations_df_dict = relations_df_to_dict(relations_df) num_regression.check(relations_df_dict)
def network( trace_file: Path = typer.Argument(..., exists=True, dir_okay=False, help=TRACE_FILE_HELP), area_file: Path = typer.Argument(..., exists=True, dir_okay=False, help=AREA_FILE_HELP), snap_threshold: float = typer.Option(0.001, help=SNAP_THRESHOLD_HELP), determine_branches_nodes: bool = typer.Option( True, help= "Whether to determine branches and nodes as part of analysis. Recommended.", ), name: Optional[str] = typer.Option( None, help="Name for Network. Used when saving outputs and as plot titles."), circular_target_area: bool = typer.Option( False, help="Is/are target area(s) circles?"), truncate_traces: bool = typer.Option( True, help="Whether to cut traces at target area boundary. Recommended."), censoring_area: Optional[Path] = typer.Option( None, help= "Path to area data that delineates censored areas within target areas.", ), branches_output: Optional[Path] = typer.Option( None, help="Where to save branch data."), nodes_output: Optional[Path] = typer.Option( None, help="Where to save node data."), general_output: Optional[Path] = typer.Option( None, help="Where to save general network analysis outputs e.g. plots."), parameters_output: Optional[Path] = typer.Option( None, help="Where to save numerical parameter data from analysis."), ): """ Analyze the geometry and topology of trace network. """ network_name = name if name is not None else area_file.stem console = Console() console.print( Text.assemble("Performing network analysis of ", (network_name, "bold green"), ".")) network = Network( trace_gdf=read_geofile(trace_file), area_gdf=read_geofile(area_file), snap_threshold=snap_threshold, determine_branches_nodes=determine_branches_nodes, name=network_name, circular_target_area=circular_target_area, truncate_traces=truncate_traces, censoring_area=read_geofile(censoring_area) if censoring_area is not None else gpd.GeoDataFrame(), ) ( general_output_path, branches_output_path, nodes_output_path, parameters_output_path, ) = default_network_output_paths( network_name=network_name, general_output=general_output, branches_output=branches_output, nodes_output=nodes_output, parameters_output=parameters_output, ) # Save branches and nodes console.print( Text.assemble( "Saving branches to ", (str(branches_output_path), "bold green"), " and nodes to ", (str(nodes_output_path), "bold green"), ".", )) network.branch_gdf.to_file(branches_output_path, driver="GPKG") network.node_gdf.to_file(nodes_output_path, driver="GPKG") console.print(rich_table_from_parameters(network.parameters)) pd.DataFrame([network.numerical_network_description() ]).to_csv(parameters_output_path) console.print( Text.assemble( "Saving extensive network parameter csv to path:\n", (str(parameters_output_path), "bold green"), )) # Plot ternary XYI-node proportion plot fig, _, _ = network.plot_xyi() save_fig(fig=fig, results_dir=general_output_path, name="xyi_ternary_plot") # Plot ternary branch proportion plot fig, _, _ = network.plot_branch() save_fig(fig=fig, results_dir=general_output_path, name="branch_ternary_plot") # Plot trace azimuth rose plot _, fig, _ = network.plot_trace_azimuth() save_fig(fig=fig, results_dir=general_output_path, name="trace_azimuth") # Plot trace length distribution plot _, fig, _ = network.plot_trace_lengths() save_fig(fig=fig, results_dir=general_output_path, name="trace_length_distribution")
def network_extensive_testing( network: Network, traces: gpd.GeoDataFrame, area: gpd.GeoDataFrame, snap_threshold: float, ): """ Test Network attributes extensively. """ # Test resetting copy_trace_gdf = network.trace_data._line_gdf.copy() copy_branch_gdf = network.branch_data._line_gdf.copy() # Test resetting network.reset_length_data() assert_frame_equal(copy_trace_gdf, network.trace_data._line_gdf) assert_frame_equal(copy_branch_gdf, network.branch_data._line_gdf) network_anisotropy = network.anisotropy assert isinstance(network_anisotropy, tuple) assert isinstance(network_anisotropy[0], np.ndarray) assert isinstance(network.trace_length_set_array, np.ndarray) assert isinstance(network.branch_length_set_array, np.ndarray) # Test passing old branch and node data branch_copy = network.branch_gdf.copy() network_test = Network( trace_gdf=traces, area_gdf=area, name="teeest_with_old", branch_gdf=branch_copy, node_gdf=network.node_gdf.copy(), determine_branches_nodes=False, truncate_traces=True, snap_threshold=snap_threshold, ) assert_frame_equal(network_test.branch_gdf, branch_copy) # Test plotting fig_returns = network.plot_branch() assert fig_returns is not None fig, ax, tax = fig_returns assert isinstance(fig, Figure) assert isinstance(ax, Axes) assert isinstance(tax, TernaryAxesSubplot) plt.close("all") # Test plotting fig_returns = network.plot_xyi() assert fig_returns is not None fig, ax, tax = fig_returns assert isinstance(fig, Figure) assert isinstance(ax, Axes) assert isinstance(tax, TernaryAxesSubplot) plt.close("all") for plot in ( "plot_parameters", "plot_anisotropy", "plot_trace_azimuth_set_count", "plot_branch_azimuth_set_count", "plot_trace_length_set_count", "plot_branch_length_set_count", "plot_trace_azimuth", "plot_branch_azimuth", "plot_trace_lengths", "plot_branch_lengths", "plot_trace_azimuth_set_lengths", "plot_branch_azimuth_set_lengths", ): # Test plotting fig_returns = getattr(network, plot)() assert fig_returns is not None if len(fig_returns) == 2: fig, ax = fig_returns elif len(fig_returns) == 3 and "length" in plot: other, fig, ax = fig_returns if not isinstance(other, powerlaw.Fit): # assume fits, figs, axes from plot_*_set_lengths assert isinstance(other, list) assert isinstance(other[0], powerlaw.Fit) # Check just the first value of returns assert len(other) == len(fig) assert len(other) == len(ax) assert len(other) == len(network.azimuth_set_names) other, fig, ax = other[0], fig[0], ax[0] elif len(fig_returns) == 3 and "azimuth" in plot: other, fig, ax = fig_returns assert isinstance(other, AzimuthBins) else: raise ValueError("Expected 3 max returns.") assert isinstance(fig, Figure) assert isinstance(ax, (Axes, PolarAxes)) plt.close("all")
def test_network( traces, area, name, determine_branches_nodes, truncate_traces, snap_threshold, circular_target_area, file_regression, data_regression, ): """ Test Network object creation and attributes with general datasets. Tests for regression and general assertions. """ network = Network( trace_gdf=traces, area_gdf=area, name=name, determine_branches_nodes=determine_branches_nodes, truncate_traces=truncate_traces, snap_threshold=snap_threshold, trace_length_set_names=("a", "b"), branch_length_set_names=("A", "B"), trace_length_set_ranges=((0.1, 1), (1, 2)), branch_length_set_ranges=((0.1, 1), (1, 2)), circular_target_area=circular_target_area, ) assert area.shape[0] == len(network.representative_points()) numerical_description = network.numerical_network_description() assert isinstance(numerical_description, dict) for key, item in numerical_description.items(): assert isinstance(key, str) if not isinstance(item, (int, float, str)): assert isinstance(item.item(), (int, float)) cut_off = 1.0 num_description_explicit_cut_offs = network.numerical_network_description( trace_lengths_cut_off=cut_off, branch_lengths_cut_off=cut_off ) for label in ("branch", "trace"): key = label + ( " " + length_distributions.Dist.POWERLAW.value + " " + length_distributions.CUT_OFF ) assert num_description_explicit_cut_offs[key] == 1.0 network_target_areas = network.target_areas assert isinstance(network_target_areas, list) assert all( [isinstance(val, (Polygon, MultiPolygon)) for val in network_target_areas] ) network_attributes = dict() for attribute in ("node_counts", "branch_counts"): # network_attributes[attribute] = getattr(network, attribute) for key, value in getattr(network, attribute).items(): if not np.isnan(value): network_attributes[key] = int(value) for key, value in numerical_description.items(): if isinstance(value, (float, int)): network_attributes[key] = round( value.item() if hasattr(value, "item") else value, 2 ) data_regression.check(network_attributes) if determine_branches_nodes and network.branch_gdf.shape[0] < 500: sorted_branch_gdf = network.branch_gdf.sort_index() assert isinstance(sorted_branch_gdf, gpd.GeoDataFrame) # Do not check massive branch counts file_regression.check(sorted_branch_gdf.to_json(indent=1)) network_extensive_testing( network=network, traces=traces, area=area, snap_threshold=snap_threshold ) trace_intersects = network.trace_intersects_target_area_boundary assert isinstance(trace_intersects, np.ndarray) assert trace_intersects.dtype in ("int32", "int64") branch_intersects = network.branch_intersects_target_area_boundary assert isinstance(branch_intersects, np.ndarray) assert network.branch_intersects_target_area_boundary.dtype in ("int32", "int64")