Skip to content

polychron.util Module

MonotonicTimer

A monotonic timer, for capturing execution time for sections of code

start

start() -> 'MonotonicTimer'

Record the start of the timed period

Returns:

Type Description
'MonotonicTimer'

returns itself, so that .start can be chained on the ctor.

stop

stop() -> 'MonotonicTimer'

Record the end of the timed period

Returns:

Type Description
'MonotonicTimer'

returns itself, so that .elapsed/elapsed_ns can be chained on calls to stop.

Raises:

Type Description
RuntimeError

stop must be called after start

elapsed_ns

elapsed_ns() -> int

Get the elapsed time in nanoseconds

Timing precision is platform specific, see the time.monotonic_ns docs

Returns:

Type Description
int

Elapsed time between the most recent calls to start and stop in nanoseconds

Raises:

Type Description
RunmtimeError

start() and stop() must have been called before elapsed_ns

elapsed

elapsed() -> float

Get the elapsed time in seconds.

Timing precision is platform specific, see the time.monotonic_ns docs

Returns:

Type Description
float

Elapsed time between the most recent calls to start and stop in fractional seconds

Raises:

Type Description
RunmtimeError

start() and stop() must have been called before elapsed_ns

trim

trim(im_trim: Image) -> Image.Image

Trims images down, cropping out dark background (<= 100/255 in all channels) unless the whole image is considerd dark (<= 100/255 in all channels)

Parameters:

Name Type Description Default
im_trim Image

Input image to trim

required

Returns:

Type Description
Image

Potentially cropped version on im_trim

bbox_from_polygon

bbox_from_polygon(points_str: str) -> list[float]

Get the axis aligned bounding box from an svg polygon points attribute, supporting rectangles (box) and kites (diamond).

Parameters:

Name Type Description Default
points_str str

the points attribute from an svg

required

Returns:

Type Description
list[float]

The 4 floating point coordinates defining the axis aligned bounding box for click detection, as [x0, x1, y0, y1]

Todo
  • Gracefully handle bad input strings
  • Todo pass in the transform values as an alt to -1*?

bbox_from_ellipse

bbox_from_ellipse(cx: float, cy: float, rx: float, ry: float) -> list[float]

Get the axis aligned bounding box for an ellipse, using the ellipse parameters from svg attributes.

Parameters:

Name Type Description Default
cx float

the cx attribute from the ellipse, as a float

required
cy float

the cy attribute from the ellipse, as a float

required
rx float

the rx attribute from the ellipse, as a float

required
ry float

the ry attribute from the ellipse, as a float

required

Returns:

Type Description
list[float]

The 4 floating point coordinates defining the axis aligned bounding box for click detection, as [x0, x1, y0, y1]

Todo
  • Gracefully handle bad inputs
  • Todo pass in the transform values as an alt to -1*?

rank_func

rank_func(tes: dict[str, list[str]], dot_str: str) -> str

adds strings into dot string to make nodes of the same group the same rank

Parameters:

Name Type Description Default
tes dict[str, list[str]]

a dictionary

required
dot_str str

The original dot/gv string

required

Returns:

Type Description
str

The mutated dot/gv string

Todo
  • Rename parameters and variables
  • Use for key, x_rank in tes.items()
  • find the closing } of the digraph instead of always getting rid of the final 2 chars
  • use ",".join(x_rank) and an fstring
  • Close the digraph on it's own line (i.e. add a newline at the end / don't include the [:-1])

node_coords_from_svg_string

node_coords_from_svg_string(svg_string: str) -> tuple[dict[str, list[float]], list[float]]

Get the coordinates of each node from a string containing the SVG representation of a graphviz DiGraph.

Parameters:

Name Type Description Default
svg_string str

The SVG version of a DiGraph produced by graphviz

required

Returns:

Type Description
tuple[dict[str, list[float]], list[float]]

A tuple of a dictionary of axis aligned bounding boxes for each node [x0, x1, y0, y1], and svg scale information [w, h]. Coordinates use a top-left origin.

Note

This function depends upon graphviz producing svg files with the expected structure

node_coords_from_svg

node_coords_from_svg(svg_file: str | Path | IO[str]) -> tuple[dict[str, list[float]], list[float]]

Get the coordinates of each node from a Directed Acyclic Graph from an SCG file on disk

Parameters:

Name Type Description Default
svg_file str | Path | IO[str]

path to the svg file on disk, or an open file handle

required

Returns:

Type Description
tuple[dict[str, list[float]], list[float]]

A tuple of a dictionary of axis aligned bounding boxes for each node [x0, x1, y0, y1], and svg scale information [w, h]. Coordinates use a top-left origin.

node_coords_from_dag

node_coords_from_dag(graph: DiGraph | Dot) -> tuple[dict[str, list[float]], list[float]]

Get the coordinates of each node from a Directed Acyclic Graph via SVG rendering in graphviz.

Parameters:

Name Type Description Default
graph DiGraph | Dot

The networkx or pydot Graph

required

Returns:

Type Description
tuple[dict[str, list[float]], list[float]]

A tuple of a dictionary of axis aligned bounding boxes for each node [x0, x1, y0, y1], and svg scale information [w, h]. Coordinates use a top-left origin.

node_coords_check

node_coords_check(coords: tuple[float, float], img_size: tuple[int, int], img_scale: float, node_coords: dict[str, list[float]], node_coords_scale: list[float]) -> str | None

Return the node at the provided coordinates, using data extracted from an svg, with potential zooming and panning of an image

Parameters:

Name Type Description Default
coords tuple[float, float]

the (x, y) target coordinates in UI-space, accounting for panning

required
img_size tuple[int, int]

the (w, h) of the image on disk

required
img_scale float

the zoom level for the image in UI-space

required
node_coords dict[str, list[float]]

the axis aligned bounding box coordinates in svg-space for each node

required
node_coords_scale list[float]

The (w, h) of the svg for coordinate translation

required

Returns:

Type Description
str | None

the id of the node selected, or None

phase_info_func

phase_info_func(file_graph: DiGraph) -> Tuple[dict[Any, Any], list[Any], list[Any], list[dict[Any, Any]]]

Returns a dictionary of phases and nodes in each phase

Parameters:

Name Type Description Default
file_graph DiGraph

A networkx graph to extract phase information from

required

Returns:

Type Description
dict[Any, Any]

A tuple of results:

list[Any]
  • A Dictionary of phases
list[Any]
  • A list of
list[dict[Any, Any]]
  • A list of node labels from the graph (should be strings but not guaranteed)
Tuple[dict[Any, Any], list[Any], list[Any], list[dict[Any, Any]]]
  • A list of node attribute dictionaries, in the order of node_list
Todo

This has not been fully-reimplemented for cases where stratigraphic data was provided via .dot/.gv file, see FILE_INPUT

edge_of_phase

edge_of_phase(test1: list[Any], pset: list[str], node_list: list[Any], node_info: list[dict[str[Any]]]) -> tuple[list[str], list[str], Iterable[str], list[tuple[Any, str]], dict[str, list[str]]]

Find nodes on edge of each phase

Parameters:

Name Type Description Default
test1 list[Any]

a list of graph nodes (contexts and phases boundary nodes) ordered as a line_graph

required
pset list[str]

a list of (unique) groups/phases

required
node_list list[Any]

a list of node labels

required
node_info list[dict[str[Any]]]

a list of node attribute dictionaries, in the same order as node_list

required

Returns:

Type Description
list[str]

A tuple of:

list[str]
  • a list of ???
Iterable[str]
  • a list of ???
list[tuple[Any, str]]
  • An iterable (dict_keys) of groups/phases
dict[str, list[str]]
  • a list of phases being tracked?
tuple[list[str], list[str], Iterable[str], list[tuple[Any, str]], dict[str, list[str]]]
  • a dictionary containing a list of nodes per group/phase
Todo
  • This has not been fully-reimplemented for cases where stratigraphic data was provided via .dot/.gv file, see FILE_INPUT
  • Returning the keys and dictionary is redundant - only need to return the dict.

node_del_fixed

node_del_fixed(graph: DiGraph, node: str) -> nx.DiGraph

Remove a node from the graph, replacing edges where possible.

A new edge will not be created if the relative relationship between two nodes is already provided through another context.

I.e. given the following graph of 4 nodes

   flowchart LR
       a --> b0
       a --> b1
       b0 --> c
       b1 --> c

Deleting b0 will produce the following, without a direct edge from a --> c

   flowchart LR
       a --> b1
       b1 --> c

Parameters:

Name Type Description Default
graph DiGraph

The graph to modify

required
node str

the name of the node to be removed

required

Returns:

Type Description
DiGraph

The mutated graph

Raises:

Type Description
NetworkXError

If the provided node is not a member of the graph

Todo
  • The input parameter is mutated, and returned by the function. This should probably either return a mutated copy or not return the graph.

all_node_info

all_node_info(node_list: List[Any], x_image: List[str], node_info: List[Any]) -> List[Any]

obtains node attributes from original dot file

phase_length_finder

phase_length_finder(con_1: str, con_2: str, group_limits: dict[str, list[float]]) -> List[Any]

Find the group/phase length between any two contexts or phase boundaries

Parameters:

Name Type Description Default
con_1 str

Context or phase boundary node id to find the phase length between

required
con_2 str

Context or phase boundary node id to find the phase length between

required
group_limits dict[str, list[float]]

Dictionary containing a list of group limits from MCMC calibration per context/phase_boundary label in the chronological_dag

required

Returns:

Type Description
List[Any]

List of potential phase durations in years.

List[Any]

An empty list is returned if either context label is not present in the mcmc calibration data.

List[Any]

If the length of samples is not the same for the provided contexts, the shorter value is used. This should not occur in regular usage.

Todo
  • Special case handling when con_1 == con_2?

imagefunc

imagefunc(dotfile: Any) -> Any

Sets note attributes to a dot string from the provided file

phase_relabel

phase_relabel(graph: DiGraph) -> nx.DiGraph

Relabels the phase labels to be alphas and betas, for display only, still refer to them with a's and b's

Parameters:

Name Type Description Default
graph DiGraph

The graph to modify

required

Returns:

Type Description
DiGraph

The mutated graph

Todo
  • The input parameter is mutated, and returned by the function. This should probably either return a mutated copy or not return the graph.
  • Ideally this should not create labels for nodes which do not start with a_/b_ but contain them, and ideally should only mutate for valid groups, in case user provided context labels include a_ (not currently prevented, although it may be as a workaround)

alp_beta_node_add

alp_beta_node_add(group: str, graph: DiGraph) -> None

Adds an alpha and beta node for the named group to the graph

Parameters:

Name Type Description Default
group str

The group label to add alpha and beta nodes for

required
graph DiGraph

The graph to be mutated

required

phase_labels

phase_labels(phi_ref: list[str], post_group: list[Literal['abutting', 'gap', 'overlap', 'end'] | GroupRelationship], phi_accept: list[list[float]], all_samps_phi: list[list[float]]) -> tuple[list[str], dict[str, list[float]], dict[str, list[float]]]

Provides group/phase limits for a group/phase

Parameters:

Name Type Description Default
phi_ref list[str]

Ordered list of group/phase labels

required
post_group list[Literal['abutting', 'gap', 'overlap', 'end'] | GroupRelationship]

Ordered list containing the relationship between the nth group, and the n+1th group

required
phi_accept list[list[float]]

Accepted group boundaries from MCMC simulation. The length of the outer list depends on the values of post_group

required
all_samps_phi list[list[float]]

all samples for group boundaries from MCMC, including rejected samples. The length of the outer list depends on the values of post_group

required

Returns:

Type Description
list[str]

A 3-element tuple of:

dict[str, list[float]]
  • The list of labels for group boundary nodes
dict[str, list[float]]
  • A dictionary of group limits from accepted phi samples
tuple[list[str], dict[str, list[float]], dict[str, list[float]]]
  • A dictionary of group limits from all phi samples

del_empty_phases

del_empty_phases(phi_ref: list[str], del_phase: set[str], phasedict: dict[tuple[str, str], str]) -> list[list[str]]

Checks for any phase rels that need changing due to missing dates

Parameters:

Name Type Description Default
phi_ref list[str]

Ordered list of group/phase labels

required
del_phase set[str]

Groups/Phases which should be removed

required
phasedict dict[tuple[str, str], str]

Dictionary of directed relationships between groups

required

Returns:

Type Description
list[list[str]]

List of pairs of group labels which a new 'gap' relationship should be created for

Todo

Should duplicates be removed from the retuned list?

group_rels_delete_empty

group_rels_delete_empty(file_graph: DiGraph, new_group_rels: list[list[str]], p_list: list[tuple[str, str]], phasedict: dict[tuple[str, str], str], phase_nodes: list[str], graph_data: tuple[DiGraph, list[list[Any]], list[Any], list[Any]]) -> Tuple[nx.DiGraph, List[str], Dict[str, str]]

Adds edges between phases that had gaps due to no contexts being left in them

Parameters:

Name Type Description Default
file_graph DiGraph

The input graph

required
new_group_rels list[list[str]]

list of new edges to create. Each element must be a list/tuple of 2 group labels.

required
p_list list[tuple[str, str]]

a list of (unique) groups/phases, each contianing 2 elements.

required
phasedict dict[tuple[str, str], str]

A dictionary containing the type of relationship between two phases

required
phase_nodes list[str]

a list of group/phase node labels, which is modified to include newly created nodes

required
graph_data tuple[DiGraph, list[list[Any]], list[Any], list[Any]]

A collection of graph_data information

required

Returns:

Type Description
DiGraph

A tuple of:

List[str]
  • The modified DiGraph
Dict[str, str]
  • A list of groups/phases which need to be remove
Tuple[DiGraph, List[str], Dict[str, str]]
  • A dictionary of newly combined group/phase boundary nodes (i.e. a_x = b_y) and the associated label
Todo
  • phase_nodes is only written to, never read from. Can it be removed?

chrono_edge_add

chrono_edge_add(file_graph: DiGraph, graph_data, xs_ys, phasedict: dict[tuple[str, str], str], phase_trck: list[tuple[str, str]], post_dict: dict[str, Literal['abutting', 'gap', 'overlap'] | GroupRelationship], prev_dict: dict[str, Literal['abutting', 'gap', 'overlap'] | GroupRelationship]) -> Tuple[nx.DiGraph, List[Any], List[str]]

chrono_edge_add

chrono_edge_remov

chrono_edge_remov(file_graph: DiGraph) -> tuple[nx.DiGraph, list[list[Any]], list[Any], list[Any]]

Removes any excess edges so we can render the chronograph

Parameters:

Name Type Description Default
file_graph DiGraph

The input graph

required

Returns:

Type Description
DiGraph

A tuple containing:

list[list[Any]]
  • A tuple of Graph data, containing:
  • phases in a dict and nodes at the edge of that phase
  • phases for each context
  • contexts
  • phases in "below form"
  • dictionary of phases and contexts in them
list[Any]
  • A list containing 2 elements [xs, ys] which gives any edges removed becuase phase boundaries need to go in
list[Any]
  • A list of phases

edge_label

edge_label(src: str, dst: str) -> str

Renders a string for the edge from src to dst.

Used for deleted edges

Previously part of Start_page.edge_render

Parameters:

Name Type Description Default
src str

The context label for the start of the directed edge

required
dst str

The context label for the destination of the directed edge

required

remove_invalid_attributes_networkx_lt_3_4

remove_invalid_attributes_networkx_lt_3_4(graph: DiGraph) -> nx.DiGraph

Function which removes 'contraction' attribtues from the provided networkx DiGraph if they are set and networkx is less than 3.4.

This is required for networkx-pydot interaction, as networkx < 3.4 does not correctly escape these values in it's pydot interface, leading to unescaped : errors.

Networkx 3.4 requires python 3.10+, so while python 3.9 support is required we must also support older networkx releases which include this bug.

Parameters:

Name Type Description Default
graph DiGraph

The DiGraph to remove contraction attributes from

required

Returns:

Type Description
DiGraph

The graph with invalid contraction attribtues removed

get_right_click_binding

get_right_click_binding(double: bool = False) -> str

Get the platform-specific tkinter binding for right-clicking.

This is different on a Mac than everywhere else

Parameters:

Name Type Description Default
double bool

if the bind should be for double clicking

False

Returns:

Type Description
str

the bind string for right click

luminance

luminance(rgb: tuple[float, float, float]) -> float

Given an rgb-tuple in normalised srgb space, return the percieved luminance

Paramters

rgb: an 3-tuple of RGB values as floats in the range [0, 1]

Returns:

Type Description
float

Percieved relative luminance of the provided srgb colour

contrast_ratio

contrast_ratio(a: float, b: float) -> float

Get the contrast ratio between relative luminance values

Parameters:

Name Type Description Default
a float

a luminance value to compare

required
b float

the other luminance value to compare

required

Returns:

Type Description
float

The contrast ratio