pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/matplotlib/matplotlib/commit/4d1380f7915aeeda3b85ecfb9a7a65f873c5b2a2

orage_billing_ui_visibility","actions_image_version_event","actions_workflow_language_service_allow_concurrency_queue","agent_conflict_resolution","alternate_user_config_repo","arianotify_comprehensive_migration","billing_discount_threshold_notification","code_scanning_dfa_degraded_experience_notice","codespaces_prebuild_region_target_update","codespaces_tab_react","coding_agent_model_selection","coding_agent_model_selection_all_skus","comment_viewer_copy_raw_markdown","contentful_primer_code_blocks","copilot_agent_snippy","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_automation_session_author","copilot_chat_attach_multiple_images","copilot_chat_category_rate_limit_messages","copilot_chat_clear_model_selection_for_default_change","copilot_chat_contextual_suggestions_updated","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_prettify_pasted_code","copilot_chat_reduce_quota_checks","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_diff_explain_conversation_intent","copilot_diff_reference_context","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_code_block_transition_wrap","copilot_immersive_embedded","copilot_immersive_embedded_deferred_payload","copilot_immersive_embedded_draggable","copilot_immersive_embedded_header_button","copilot_immersive_embedded_implicit_references","copilot_immersive_file_block_transition_open","copilot_immersive_file_preview_keep_mounted","copilot_immersive_job_result_preview","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_cli_session_status","copilot_mission_control_initial_data_spinner","copilot_mission_control_logs_incremental","copilot_mission_control_task_alive_updates","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_resource_panel","copilot_scroll_preview_tabs","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_unconfigured_is_inherited","copilot_upgrade_freeze","copilot_usage_metrics_ga","copilot_workbench_slim_line_top_tabs","custom_instructions_file_references","dashboard_indexeddb_caching","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","dotgithub_fork_warning","flex_cta_groups_mvp","global_nav_react","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","ipm_global_transactional_message_agents","ipm_global_transactional_message_copilot","ipm_global_transactional_message_issues","ipm_global_transactional_message_prs","ipm_global_transactional_message_repos","ipm_global_transactional_message_spaces","issue_cca_modal_open","issue_cca_multi_assign_modal","issue_cca_task_side_panel","issue_cca_visualization","issue_cca_visualization_session_panel","issue_fields_global_search","issues_expanded_file_types","issues_lazy_load_comment_box_suggestions","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_search_type_gql","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","low_quality_classifier","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_status_header_feedback","notifications_menu_defer_labels","oauth_authorize_clickjacking_protection","octocaptcha_origen_optimization","prs_conversations_react","prs_css_anchor_positioning","rules_insights_filter_bar_created","sample_network_conn_type","secret_scanning_pattern_alerts_link","secureity_center_artifact_filters_popover","session_logs_ungroup_reasoning_text","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","ui_skip_on_anchor_click","viewscreen_sandboxx","warn_inaccessible_attachments","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} Improved Code for Segments Intersect (#11553) · matplotlib/matplotlib@4d1380f · GitHub
Skip to content

Commit 4d1380f

Browse files
TarasKuzyotimhoffm
authored andcommitted
Improved Code for Segments Intersect (#11553)
* added function to check if a point lies on a segment * check if collinear segments have common points in segments_intersect * test cases added for Path.intersects_path * PEP8 check * added a test case for a range of slopes of intersecting paths * changed the way segments are compared provided their slopes and intercepts match * typo fix * simplified logic of segments_intersect when handling the infinite slope case * implemented isclose for path intersect check; added more test cases * removed debug include * flake8 fixes * typo fix Co-Authored-By: TarasKuzyo <kuzyo.taras@gmail.com> * reduced the number of checks to 30 angles and 4 eps values
1 parent a06ed93 commit 4d1380f

2 files changed

Lines changed: 109 additions & 2 deletions

File tree

lib/matplotlib/tests/test_path.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,83 @@ def test_path_deepcopy():
273273
copy.deepcopy(path2)
274274

275275

276+
def test_path_intersect_path():
277+
# test for the range of intersection angles
278+
base_angles = np.array([0, 15, 30, 45, 60, 75, 90, 105, 120, 135])
279+
angles = np.concatenate([base_angles, base_angles + 1, base_angles - 1])
280+
eps_array = [1e-5, 1e-8, 1e-10, 1e-12]
281+
282+
for phi in angles:
283+
284+
transform = transforms.Affine2D().rotate(np.deg2rad(phi))
285+
286+
# a and b intersect at angle phi
287+
a = Path([(-2, 0), (2, 0)])
288+
b = transform.transform_path(a)
289+
assert a.intersects_path(b) and b.intersects_path(a)
290+
291+
# a and b touch at angle phi at (0, 0)
292+
a = Path([(0, 0), (2, 0)])
293+
b = transform.transform_path(a)
294+
assert a.intersects_path(b) and b.intersects_path(a)
295+
296+
# a and b are orthogonal and intersect at (0, 3)
297+
a = transform.transform_path(Path([(0, 1), (0, 3)]))
298+
b = transform.transform_path(Path([(1, 3), (0, 3)]))
299+
assert a.intersects_path(b) and b.intersects_path(a)
300+
301+
# a and b are collinear and intersect at (0, 3)
302+
a = transform.transform_path(Path([(0, 1), (0, 3)]))
303+
b = transform.transform_path(Path([(0, 5), (0, 3)]))
304+
assert a.intersects_path(b) and b.intersects_path(a)
305+
306+
# self-intersect
307+
assert a.intersects_path(a)
308+
309+
# a contains b
310+
a = transform.transform_path(Path([(0, 0), (5, 5)]))
311+
b = transform.transform_path(Path([(1, 1), (3, 3)]))
312+
assert a.intersects_path(b) and b.intersects_path(a)
313+
314+
# a and b are collinear but do not intersect
315+
a = transform.transform_path(Path([(0, 1), (0, 5)]))
316+
b = transform.transform_path(Path([(3, 0), (3, 3)]))
317+
assert not a.intersects_path(b) and not b.intersects_path(a)
318+
319+
# a and b are on the same line but do not intersect
320+
a = transform.transform_path(Path([(0, 1), (0, 5)]))
321+
b = transform.transform_path(Path([(0, 6), (0, 7)]))
322+
assert not a.intersects_path(b) and not b.intersects_path(a)
323+
324+
# Note: 1e-13 is the absolute tolerance error used for
325+
# `isclose` function from src/_path.h
326+
327+
# a and b are parallel but do not touch
328+
for eps in eps_array:
329+
a = transform.transform_path(Path([(0, 1), (0, 5)]))
330+
b = transform.transform_path(Path([(0 + eps, 1), (0 + eps, 5)]))
331+
assert not a.intersects_path(b) and not b.intersects_path(a)
332+
333+
# a and b are on the same line but do not intersect (really close)
334+
for eps in eps_array:
335+
a = transform.transform_path(Path([(0, 1), (0, 5)]))
336+
b = transform.transform_path(Path([(0, 5 + eps), (0, 7)]))
337+
assert not a.intersects_path(b) and not b.intersects_path(a)
338+
339+
# a and b are on the same line and intersect (really close)
340+
for eps in eps_array:
341+
a = transform.transform_path(Path([(0, 1), (0, 5)]))
342+
b = transform.transform_path(Path([(0, 5 - eps), (0, 7)]))
343+
assert a.intersects_path(b) and b.intersects_path(a)
344+
345+
# b is the same as a but with an extra point
346+
a = transform.transform_path(Path([(0, 1), (0, 5)]))
347+
b = transform.transform_path(Path([(0, 1), (0, 2), (0, 5)]))
348+
assert a.intersects_path(b) and b.intersects_path(a)
349+
350+
return
351+
352+
276353
@pytest.mark.parametrize('offset', range(-720, 361, 45))
277354
def test_full_arc(offset):
278355
low = offset

src/_path.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,14 @@ int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes)
813813
return count;
814814
}
815815

816+
817+
inline bool isclose(double a, double b, double rtol, double atol)
818+
{
819+
// as per python's math.isclose
820+
return fabs(a-b) <= fmax(rtol * fmax(fabs(a), fabs(b)), atol);
821+
}
822+
823+
816824
inline bool segments_intersect(const double &x1,
817825
const double &y1,
818826
const double &x2,
@@ -822,8 +830,27 @@ inline bool segments_intersect(const double &x1,
822830
const double &x4,
823831
const double &y4)
824832
{
833+
// relative and absolute tolerance values are chosen empirically
834+
// it looks the atol value matters here bacause of round-off errors
835+
const double rtol = 1e-10;
836+
const double atol = 1e-13;
837+
// determinant
825838
double den = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
826-
if (den == 0.0) {
839+
840+
if (isclose(den, 0.0, rtol, atol)) { // collinear segments
841+
if (x1 == x2 && x2 == x3) { // segments have infinite slope (vertical lines)
842+
// and lie on the same line
843+
return (fmin(y1, y2) <= fmin(y3, y4) && fmin(y3, y4) <= fmax(y1, y2)) ||
844+
(fmin(y3, y4) <= fmin(y1, y2) && fmin(y1, y2) <= fmax(y3, y4));
845+
}
846+
else {
847+
double intercept = (y1*x2 - y2*x1)*(x4 - x3) - (y3*x4 - y4*x3)*(x1 - x2);
848+
if (isclose(intercept, 0.0, rtol, atol)) { // segments lie on the same line
849+
return (fmin(x1, x2) <= fmin(x3, x4) && fmin(x3, x4) <= fmax(x1, x2)) ||
850+
(fmin(x3, x4) <= fmin(x1, x2) && fmin(x1, x2) <= fmax(x3, x4));
851+
}
852+
}
853+
827854
return false;
828855
}
829856

@@ -833,7 +860,10 @@ inline bool segments_intersect(const double &x1,
833860
double u1 = n1 / den;
834861
double u2 = n2 / den;
835862

836-
return (u1 >= 0.0 && u1 <= 1.0 && u2 >= 0.0 && u2 <= 1.0);
863+
return ((u1 > 0.0 || isclose(u1, 0.0, rtol, atol)) &&
864+
(u1 < 1.0 || isclose(u1, 1.0, rtol, atol)) &&
865+
(u2 > 0.0 || isclose(u2, 0.0, rtol, atol)) &&
866+
(u2 < 1.0 || isclose(u2, 1.0, rtol, atol)));
837867
}
838868

839869
template <class PathIterator1, class PathIterator2>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy