Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/unit tests #81

Merged
merged 25 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b46b76e
ext: adding pytest files
sdat2 Jul 13, 2022
22ff600
ext: adding pytest files
sdat2 Jul 13, 2022
c460548
fix: adding more test dependencies for geograph/demo
sdat2 Jul 13, 2022
eaaca0b
fix: adding more test dependencies for geograph/demo
sdat2 Jul 13, 2022
5d1a91c
ext: adding sudo apt get commands to try to fix rasterio install
sdat2 Jul 13, 2022
91ea106
ext: reformatting etc.
sdat2 Jul 13, 2022
d4579e2
ext: more reformatting
sdat2 Jul 13, 2022
764410e
ext: adding ipynb
sdat2 Jul 13, 2022
c3efeda
fix: pytest now working (without any tests)
sdat2 Jul 13, 2022
5fe0831
ext: changing create test data so that it runs as a test
sdat2 Jul 14, 2022
f8c3f90
reformatting
sdat2 Jul 14, 2022
33d69b5
ext: adding build-essential to .github actions
sdat2 Jul 14, 2022
3ee715a
ext: trying with macos in github actions
sdat2 Jul 14, 2022
9917889
fix? unpinning rasterio
sdat2 Jul 16, 2022
a41f62c
fix? remove sudo apt-get section of github action
sdat2 Jul 16, 2022
0fcecf1
ext: add windows to actions
sdat2 Jul 16, 2022
08c14ef
fix? adding more requirments to fix windows gdal issue
sdat2 Jul 16, 2022
dba21c6
fix? trying something else
sdat2 Jul 16, 2022
8118159
fix? adding back in pipwin
sdat2 Jul 16, 2022
e78a24f
fix: missing comma
sdat2 Jul 16, 2022
84b4885
give up on windows
sdat2 Jul 18, 2022
001cced
ext: removing descartes, pipwin, flake8; adding in matplotlib, seaborn
sdat2 Jul 21, 2022
fe1892f
fix: typo in REQUIRED
sdat2 Jul 21, 2022
9ac62d8
fix: ci rename branch to main, ext: adding single doc unit test for p…
sdat2 Jul 21, 2022
77de58f
fix: fixing unit test import
sdat2 Jul 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python package

on:
push:
branches: [ "main", "feature/unit-tests" ]
pull_request:
branches: [ "main" ]

jobs:
build:

runs-on: ${{matrix.os}}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10"]

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest matplotlib seaborn
pip install -e .
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do these error codes mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I'm not sure, I originally adapted this from a github actions example. Given that we have pylint anyway, I will get rid of this section.

# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest geograph
14 changes: 8 additions & 6 deletions docs/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import glob


def create_notebook_links():
"""Create links and entries for notebook in sphinx docs.

Expand All @@ -13,7 +14,7 @@ def create_notebook_links():
notebooks = glob.glob("./notebooks/*.ipynb")
notebooks.sort()

file_name_tmp = 'docs/notebooks/{}.nblink'
file_name_tmp = "docs/notebooks/{}.nblink"
file_content_tmp = """
{{
"path": "../../notebooks/{}.ipynb"
Expand All @@ -32,12 +33,13 @@ def create_notebook_links():
nb_name = path.split("/")[-1].split(".")[0]
file_name = file_name_tmp.format(nb_name)
file_content = file_content_tmp.format(nb_name)
with open(file_name,'w') as f:
f.write(file_content)
rst_index += "\n notebooks/{}".format(nb_name)
with open(file_name, "w") as f:
f.write(file_content)
rst_index += "\n notebooks/{}".format(nb_name)

with open("docs/tutorials.rst",'w') as f:
with open("docs/tutorials.rst", "w") as f:
f.write(rst_index)


if __name__ == "__main__":
create_notebook_links()
create_notebook_links()
3 changes: 0 additions & 3 deletions geograph/binary_graph_operations.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
"""Contains tools for binary operations between GeoGraph objects."""
from __future__ import annotations

from typing import TYPE_CHECKING, Dict, List, Tuple

import geopandas as gpd
from shapely.geometry.base import BaseGeometry
from shapely.geometry.polygon import Polygon

import geograph.utils.geopandas_utils as gpd_utils
from geograph.utils.polygon_utils import EMPTY_POLYGON, collapse_empty_polygon

Expand Down
2 changes: 1 addition & 1 deletion geograph/demo/binder_constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""This file is for constants relevant to the binder demo."""

from geograph.constants import PROJECT_PATH


# Data directory on GWS
DATA_DIR = PROJECT_PATH / "data"
# Polygon data of Chernobyl Exclusion Zone (CEZ)
Expand Down
5 changes: 2 additions & 3 deletions geograph/demo/plot_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import itertools
from distutils.spawn import find_executable
from typing import Sequence, Tuple

import matplotlib
import matplotlib.style
import numpy as np
Expand Down Expand Up @@ -142,7 +141,7 @@ def label_subplots(
def get_dim(
width: float = 600,
fraction_of_line_width: float = 1,
ratio: float = (5 ** 0.5 - 1) / 2,
ratio: float = (5**0.5 - 1) / 2,
) -> Tuple[float, float]:
"""Return figure height, width in inches to avoid scaling in latex.

Expand Down Expand Up @@ -180,7 +179,7 @@ def set_dim(
fig: matplotlib.pyplot.figure,
width: float = 600,
fraction_of_line_width: float = 1,
ratio: float = (5 ** 0.5 - 1) / 2,
ratio: float = (5**0.5 - 1) / 2,
) -> None:
"""Set aesthetic figure dimensions to avoid scaling in latex.

Expand Down
15 changes: 6 additions & 9 deletions geograph/geograph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
See https://networkx.org/documentation/stable/index.html for graph operations.
"""
from __future__ import annotations

import bz2
import gzip
import inspect
Expand All @@ -14,7 +13,6 @@
from copy import deepcopy
from itertools import zip_longest
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Union

import geopandas as gpd
import networkx as nx
import numpy as np
Expand All @@ -24,7 +22,6 @@
import shapely
from shapely.prepared import prep
from tqdm import tqdm

from geograph import binary_graph_operations, metrics
from geograph.metrics import CLASS_METRICS_DICT, Metric
from geograph.utils import rasterio_utils
Expand Down Expand Up @@ -677,12 +674,12 @@ def add_habitat(
# barrier polygon. If this results in multiple polygons,
# then the barrier polygon fully cuts the buffered polygon.
#
# We find the resulting buffered polygon fragment that contains
# the original node polygon, by selecting the polygon that
# contains a representative point sampled from the original
# polygon.
# We then check if that polygon fragment intersects the
# neighbour polygon we're trying to reach.
# We find the resulting buffered polygon fragment that contains
# the original node polygon, by selecting the polygon that
# contains a representative point sampled from the original
# polygon.
# We then check if that polygon fragment intersects the
# neighbour polygon we're trying to reach.
# If it does not, there is no path.
# If it does, there may be a path or there may not - it
# would require complex pathfinding code to discover, but
Expand Down
7 changes: 2 additions & 5 deletions geograph/geotimeline.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""Module for analysing multiple GeoGraph objects."""
from __future__ import annotations

import datetime
from bisect import bisect_left
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union

import numpy as np
import pandas as pd
import xarray as xr

from geograph import GeoGraph
from geograph.binary_graph_operations import NodeMap, identify_graphs

Expand Down Expand Up @@ -121,7 +118,7 @@ def _sort_by_time(self, reverse: bool = False) -> None:
}

def _load_from_sequence(self, graph_list: List[TimedGeoGraph]) -> None:
"""Loads the sorted list of timed geographs into the timeline. """
"""Loads the sorted list of timed geographs into the timeline."""

# Make sure list is sorted in ascending time order (earliest = left)
by_time = lambda item: item.time
Expand Down Expand Up @@ -186,7 +183,7 @@ def node_map_cache(self, time1: TimeStamp, time2: TimeStamp) -> NodeMap:
raise NotCachedError

def _empty_node_map_cache(self) -> None:
""" Empties the node map cache."""
"""Empties the node map cache."""
self._node_map_cache = dict()

def timestack(self, use_cached: bool = True) -> List[NodeMap]:
Expand Down
6 changes: 2 additions & 4 deletions geograph/metrics.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"""Functions for calculating metrics from a GeoGraph."""
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Optional, Union

import networkx as nx
import numpy as np

Expand Down Expand Up @@ -164,7 +162,7 @@ def _simpson_diversity_index(geo_graph: geograph.GeoGraph) -> Metric:
)

return Metric(
value=1 - np.sum(class_prop_of_landscape ** 2),
value=1 - np.sum(class_prop_of_landscape**2),
name="simpson_diversity_index",
description=description,
variant="conventional",
Expand Down Expand Up @@ -391,7 +389,7 @@ def _class_effective_mesh_size(
)

return Metric(
value=np.sum(class_areas ** 2) / total_area,
value=np.sum(class_areas**2) / total_area,
name=f"effective_mesh_size_class={class_value}",
description=description,
variant="conventional",
Expand Down
Empty file added geograph/tests/__init__.py
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

Note: We may want to delete this at some point.
"""
import numpy as np
from typing import Dict, Iterable, Tuple

import geopandas as gpd
import numpy as np
import pytest
from geograph.constants import SRC_PATH
from geograph.tests.utils import get_array_transform, polygonise_sub_array
from geograph.utils.rasterio_utils import polygonise
Expand Down Expand Up @@ -41,7 +41,9 @@ def _polygonise_splits(
return result


if __name__ == "__main__":
@pytest.mark.unit
def test_create_data() -> None:
"""Create the test data."""
print("Creating test data ... ")
TEST_DATA_FOLDER = SRC_PATH / "tests" / "testdata"
TEST_DATA_FOLDER.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -96,3 +98,7 @@ def _polygonise_splits(
polygons_t = polygonise(arr_t, transform=get_array_transform(arr_t))
save_path = TEST_DATA_FOLDER / "timestack" / f"time_{i}.gpkg"
polygons_t.to_file(save_path, driver="GPKG")


if __name__ == "__main__":
test_create_data()
Binary file modified geograph/tests/testdata/adjacent/full.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/adjacent/lower_left.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/adjacent/lower_right.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/adjacent/upper_left.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/adjacent/upper_right.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/overlapping/full.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/overlapping/lower_left.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/overlapping/lower_right.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/overlapping/upper_left.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/overlapping/upper_right.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/timestack/time_0.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/timestack/time_1.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/timestack/time_2.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/timestack/time_3.gpkg
Binary file not shown.
Binary file modified geograph/tests/testdata/timestack/time_4.gpkg
Binary file not shown.
3 changes: 1 addition & 2 deletions geograph/tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Convenience functions for creating and analysing test data for GeoGraph"""
from typing import Iterable, Tuple

import affine
import geograph
import geopandas as gpd
Expand All @@ -9,6 +8,7 @@
import seaborn as sns
from geograph.utils.rasterio_utils import polygonise


# Mirror the x axis
AFFINE_MIRROR_X = affine.Affine(-1, 0, 0, 0, 1, 0)
# Mirror the y axis
Expand Down Expand Up @@ -86,7 +86,6 @@ def polygonise_sub_array(
Args:
arr (np.ndarray): The numpy array from which to select the sub-array
x_lims (Tuple[int, int]): The x-limits of the sub-array. Must be >=0 or None.

y_lims (Tuple[int, int]): The y-limits of the sub-array. Must be >=0 or None.

Returns:
Expand Down
2 changes: 1 addition & 1 deletion geograph/utils/geopandas_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Helper functions for operating with geopandas objects."""
from typing import Dict, List

import geopandas as gpd
import networkx as nx
import tqdm
Expand All @@ -11,6 +10,7 @@
)
from shapely.geometry import MultiPolygon


# For switching identifiction mode in `identify_node`
_BULK_SPATIAL_IDENTIFICATION_FUNCTION = {
"corner": connect_with_interior_or_edge_or_corner_bulk,
Expand Down
2 changes: 1 addition & 1 deletion geograph/utils/polygon_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Helper functions for overlap computations with polygons in shapely."""
from typing import List

from geopandas.array import GeometryArray
from numpy import ndarray
from shapely.geometry.polygon import Polygon


# Note: All DE-9IM patterns below are streamlined to work well with polygons.
# They are not guaranteed to work on lower dimensional objects (points/lines)
CORNER_ONLY_PATTERN = "FF*F0****"
Expand Down
2 changes: 0 additions & 2 deletions geograph/utils/rasterio_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""A collection of utility functions for data loading with rasterio."""

from typing import Iterable, Optional, Tuple, Union

import affine
import geograph.utils.geopandas_utils as gpd_utils
import geopandas as gpd
Expand Down
20 changes: 9 additions & 11 deletions geograph/visualisation/control_widgets.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
"""Module with widgets to control GeoGraphViewer."""

from __future__ import annotations

import logging
from typing import Dict, Optional

import ipywidgets as widgets
import traitlets
from geograph.visualisation import geoviewer, widget_utils
Expand Down Expand Up @@ -807,20 +804,21 @@ def _hover_callback(self, feature, **kwargs): # pylint: disable=unused-argument
"""Callback function on hover on graph polygon patch"""
try:
# self.logger.debug("HoverWidget callback called.")
new_value = widget_utils.create_html_header(
"Current Patch"
).value + """</br>
new_value = (
widget_utils.create_html_header("Current Patch").value
+ """</br>
<b>Class label:</b> {}</br>
<b>Area:</b> {:.2f} ha</br>
<b>Perimeter:</b> {:.2f} km</br>
<b>Shape index:</b> {:.2f}</br>
<b>Fractal dim.:</b> {:.2f}
""".format(
feature["properties"]["class_label"],
feature["properties"]["area"] / 1e4,
feature["properties"]["perimeter"] / 1e3,
feature["properties"]["shape_index"],
feature["properties"]["fractal_dimension"],
feature["properties"]["class_label"],
feature["properties"]["area"] / 1e4,
feature["properties"]["perimeter"] / 1e3,
feature["properties"]["shape_index"],
feature["properties"]["fractal_dimension"],
)
)
if "node_dynamic" in feature["properties"].keys():
new_value += "</br><b>Node dyanmic:</b> {}".format(
Expand Down
3 changes: 0 additions & 3 deletions geograph/visualisation/folium_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
"""Module with utility functions to plot graphs in folium."""

from __future__ import annotations

from typing import Callable, List, Optional, Tuple

import folium
import geograph
import geopandas as gpd
Expand Down
Loading