Skip to content

Commit

Permalink
Created module with plotting functions for tutorial notebook (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphdewit authored Nov 15, 2024
1 parent d3436de commit 53517c2
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 143 deletions.
35 changes: 35 additions & 0 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[bumpversion]
current_version = 0.3.1
commit = True
tag = False
parse =
(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) # major, minor and patch
(?:\-(?P<dev>(?:rc|))\.(?P<prenum>\d+))? # pre-release
serialize =
{major}.{minor}.{patch}-{dev}.{prenum}
{major}.{minor}.{patch}
commit_message = "bump version {old_version} -> {new_version}"
tag_message = "{new_version}"
tag_scope = "default"

[bumpversion:part:dev]
values =
stable
rc

[bumpversion:part:prenum]
first_value = 0

[bumpversion:file:sensingcluespy/__init__.py]
search = __version__ = "{current_version}"
replace = __version__ = "{new_version}"

[bumpversion:file:docs/conf.py]
search = release = "{current_version}"
replace = release = "{new_version}"

[bdist_wheel]
universal = 1

[options.packages.find]
where = .
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
.python-version
MANIFEST

# PyInstaller
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
project = "sensingcluespy"
copyright = "2024, SensingClues"
author = "SensingClues"
release = "0.2.3"
release = "0.3.1"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
6 changes: 4 additions & 2 deletions docs/source/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ Introduction
`SensingClues <https://sensingclues.org/>`_ allows you to record, monitor and analyze wildlife observations to support nature conservation initiatives.
The package ``sensingcluespy`` allows Python-users to connect to SensingClues' database and download
data logged using the Cluey-app. This includes wildlife observations and tracks, custom map layers,
and the wildlife ontology used by SensingClues. **Note:** you need credentials for the SensingClues `Cluey <https://sensingclues.org/cluey>`_-app to
connect to the database.
and the wildlife ontology used by SensingClues.

**Note:** you need credentials for the SensingClues `Cluey <https://sensingclues.org/cluey>`_-app to
connect to the database. However, the tutorial notebook uses a read-only demo-user, allowing you to explore the available functionality, even without your own Cluey-account.



178 changes: 96 additions & 82 deletions notebooks/sensingclues_tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.4'
# jupytext_version: 1.2.0
# format_version: '1.5'
# jupytext_version: 1.16.4
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
Expand All @@ -16,18 +16,27 @@
#
# [SensingClues](https://sensingclues.org/) allows you to record, monitor and analyze wildlife observations to support nature conservation initiatives. This notebook shows the following:
#
# - **Basic**: the main SensingClues-functionality of **extracting observation and track data**.
# - **Advanced**: this section includes the usage of a hierarchy of available concepts (e.g. animal species or type of activity), which enhances reporting and analysis of the observation data. Further, we show how to collect and visualize layer data from SensingClues.
# - **Core**: the main SensingClues-functionality of
# 1. Extracting observation data
# 2. Extracting track data
# - **Advanced**: additional functionality including
# 1. A hierarchy of available concepts (e.g. animal species or type of (illegal) activity), which enhances reporting and analysis of the observation data.
# 2. Extraction and visualization of layer data from SensingClues.
#
# You can adapt this notebook to extract your own recordings. For more detail on what you can configure as a user, see the API-documentation of the `sensingcluespy`-package.
# You can adapt this notebook to extract your own observation data. For more detail on what you can configure as a user, see the API-documentation of the `sensingcluespy`-package [here](https://sensingcluespy.readthedocs.io/en/latest/).
#
# ### Before you start
#
# To run this notebook, you should:
# - create a personal account at SensingClues using the Cluey Data Collector app, which can be downloaded from the Google Playstore (not supported for iOS currently). Also see [here](https://sensingclues.org/portal).
# - install the `sensingcluespy`-package in a virtual python environment (`pip install -e .` from the main directory of the repository).
# - install the requirements in requirements.txt (if not already installed automatically in the previous step).
# - create a file '.env' in the root of the wildcat-api-python-repository, containing your SensingClues credentials. These will be read in this notebook to log in. The file should look like this:
# - Install the `sensingcluespy`-package in a virtual python environment (`pip install -e .` from the main directory of the repository).
# - Install the requirements in requirements.txt (if not already installed automatically in the previous step).
# Install the requirements in requirements_dev.txt to create the plots in this notebook using the `matplotlib` and `folium`-packages (if not already installed automatically in the previous step).
#
# #### [Optional] Create your own user account
#
# For the purpose of this tutorial, we use a **read-only** user called "demo". If you want to continue using SensingClues for your own work (of course you want to! :-) ), then please do the following:
# - Create a personal account at SensingClues using the Cluey Data Collector app, which can be downloaded from the Google Playstore (not supported for iOS currently). Also see [here](https://sensingclues.org/portal).
# - Create a file '.env' in the root of the wildcat-api-python-repository, containing your SensingClues credentials. These will be read in this notebook to log in. The file should look like this:
# ```
# # SensingClues credentials
# USERNAME=your_username
Expand All @@ -36,103 +45,124 @@

# ## Configuration

# +
# N.B. While sensingcluespy does not require you to install visualization packages, this tutorial does.
# To run this tutorial in full, please install matplotlib and folium (as contained in requirements_dev.txt).
import folium
import geopandas as gpd
import matplotlib.pyplot as plt
import os

from dotenv import load_dotenv

from sensingcluespy import sclogging
from sensingcluespy.api_calls import SensingClues
from sensingcluespy.src import helper_functions as helpers
from sensingcluespy.src import visualization as viz
# -

plt.style.use("ggplot")

logger = sclogging.get_sc_logger()
sclogging.set_sc_log_level("DEBUG")
sclogging.set_sc_log_level("INFO")

load_dotenv()

# %load_ext autoreload
# %autoreload 2

# N.B. you can place your credentials here as well, but this is not recommended.
username = os.getenv("USERNAME")
password = os.getenv("PASSWORD")
# +
# N.B. We recommend to place your credentials in an environment file and read them like so:
# username = os.getenv("USERNAME")
# password = os.getenv("PASSWORD")

# However, for the purpose of this demo, we use a read-only demo user:
username = "demo"
password = "demo"
# -


# ## Connect to SensingClues

sensing_clues = SensingClues(username, password)

# +
# you should have logged in automatically by calling the class.
# if not, you can call the login-method separately.
# status = sensing_clues.login(username, password)

# +
# It is not necessary to log out, but you can do so by calling:
# sensing_clues.logout()
# -

# ## Check available data
#
# By default, you have access to several groups of data, such as a demo dataset and a large dataset offered by [Global Forest Watch](https://www.globalforestwatch.org).

info = sensing_clues.get_groups()
info

# specify the group(s) to extract data from
# Specify the group(s) to extract data from
# For this tutorial, focus-project-1234 contains demo observations,
# while focus-project-3494596 contains demo tracks.
groups = [
"focus-project-3494596",
"focus-project-1234",
]

# ## Basic functionality
# ## Core functionality
#
# - Get observation data
# - Get track data
# Time to collect and plot some observation and track data!

# ### Get observations
#
# You can filter the extracted observations in multiple ways, such as data, coordinates (bounding box) and concepts. For full detail on the options, see the documentation of the API. Some key features are shown here:
# You can filter the extracted observations in multiple ways, such as timestamps, coordinates (bounding box) and concepts. Some key features are shown here:
#
# - **Date and time**: set `date_from` and/or `date_until` (in format %Y-%m-%d, assumes UTC).
# - **Coordinates**: set `coord`, e.g. {"north": 32, "east": 20, "south": 31, "west": 17}.
# - **Concepts**: set `concepts` to include, e.g. 'animal'. *See example shown later in this notebook*.
# - **Concepts**: set `concepts` to include, e.g. 'animal'. *See detailed example later in this notebook*.
#
# For full detail on the options, see the documentation of the API [here](https://sensingcluespy.readthedocs.io/en/latest/).
#
# #### Notes
# - Each observation has a unique `entityId` and may have multiple concepts (labels) associated with it,
# in which case the number of records in the observations-dataframe is larger than
# the number of observations mentioned by the logger.
# - Reading all data in a group can take minutes or longer, depending on the size of the dataset. If you want to do a quick test, you can limit the number of pages to read by setting `page_nbr_sample`.
# #### Usage notes
# - Reading all data in a group can take minutes or longer, depending on the size of the dataset. If you want to do a quick test, you can limit the number of pages to read by setting `page_nbr_sample`.
# - Each observation has a unique `entityId` and may have multiple concepts (labels) associated with it, in which case the number of records in the observations-dataframe is larger than the number of observations mentioned by the logger.

# a quick test can be done like so
obs_sample = sensing_clues.get_observations(groups=groups, page_nbr_sample=2)
# A quick check of the number of available records
obs_sample = sensing_clues.get_observations(groups=groups, page_nbr_sample=1)

# see the API-documentation for a full description of filter possibilities
# to filter on concepts, see example shown later in this notebook.
observations = sensing_clues.get_observations(
groups=groups,
date_until="2018-07-01",
coord={"north": 32, "east": 20, "south": 31.5, "west": 10}
date_from="2024-07-01",
coord={"north": -17, "east": 30, "south": -19, "west": 20}
)

# #### Visualize these observations
#
# The standard plotting-function `plot_observation` shows a separate layer for all observation types (typically ['community_work', 'animal', 'community', 'poi', 'hwc'], where 'poi' = 'point of interest' and 'hwc' = 'human-wildlife-conflict').

viz.plot_observations(
observations,
show_heatmap="hwc_animal",
padding=(25, 25)
)

observations.head()
# You can explore the observations per observationType like so:
observation_type = "animal"
# observation_type = "hwc"
observations.loc[observations["observationType"] == observation_type, "conceptLabel"].value_counts()

# ### Get tracks
#
# You can filter the extracted observations in multiple ways, such as data, coordinates (bounding box) and concepts, similar to `get_observations`.

tracks = sensing_clues.get_tracks(
groups=groups,
date_until="2018-07-01",
coord={"north": 32, "east": 20, "south": 31.5, "west": 10}
# date_from="2024-07-01",
# coord={"north": -17, "east": 30, "south": -19, "west": 20}
)

tracks.head()

# #### Add geosjon-data to tracks
# #### Visualize tracks
#
# If available, you can add geojson-data (including geometries) to the tracks.
# If available, you can add geojson-data (including geometries) to the tracks and subsequently visualize the tracks.

tracks_geo = sensing_clues.add_geojson_to_tracks(tracks)

track_map = viz.plot_tracks(tracks_geo["geometry"])
track_map

# ## Advanced functionality

# ### Get all available concepts and their hierarchy
Expand Down Expand Up @@ -171,7 +201,7 @@
oid = "https://sensingclues.poolparty.biz/SCCSSOntology/222"
helpers.get_label_for_id(hierarchy, oid)

# #### Does this Kite have any children?
# #### Does this Kite have any child concepts?

label = "Kite"
children_label = helpers.get_children_for_label(hierarchy, label)
Expand All @@ -184,31 +214,29 @@

# ### Filter observations on concept
#
# Here we show an example of filtering the data on these concepts.
# Here we show an example of filtering the data on concepts. The example filters on the concepts of Impala and Giraffe.
#
# **Instructions:**
# - Set `concepts` to include, e.g. 'animal', specified as a Pool Party URL, e.g. "https://sensingclues.poolparty.biz/SCCSSOntology/186".
# - Note that you can infer the URL's available for a certain common name by using the helper function `helpers.get_label_for_id(hierarchy, oid)`, as shown above.
# - Further, if you want to exclude subconcepts, i.e. keep observations with the label 'animal' but exclude observations with the label 'elephant', set `include_subconcepts=False`.
#

# +
concept_animal = [
"https://sensingclues.poolparty.biz/SCCSSOntology/308", # Impala
"https://sensingclues.poolparty.biz/SCCSSOntology/319", # Giraffe

# or infer the id using a label, for instance:
# helpers.get_id_for_label(hierarchy, "Animal sighting"),
]
observations = sensing_clues.get_observations(
concept_observations = sensing_clues.get_observations(
groups=groups,
date_until="2018-07-01",
concepts=concept_animal,
coord={"north": 32, "east": 20, "south": 31.5, "west": 10}
# date_from="2024-07-01",
# coord={"north": -17, "east": 30, "south": -19, "west": 20}
)
# -

observations.head()
concept_observations.head()

# ### Count concepts related to observations
#
Expand All @@ -219,7 +247,7 @@
# - A list of child concepts, e.g. by extracting children for the label "Animal sighting" from hierarchy (see example below).

date_from = "2010-01-01"
date_until = "2024-01-01"
date_until = "2024-08-01"
label = "Animal sighting"
children_label = helpers.get_children_for_label(hierarchy, label)
concept_counts = sensing_clues.get_concept_counts(
Expand All @@ -230,15 +258,6 @@
# #### Example: visualize concept counts
#
# To make the visualization intelligible, you can add information on labels from the `hierarchy`-dataframe.
#
# To do so, first install matplotlib.

# +
# # !pip install matplotlib
# -

import matplotlib.pyplot as plt
plt.style.use("ggplot")

min_freq = 10
if not concept_counts.empty:
Expand All @@ -265,27 +284,22 @@
layers = sensing_clues.get_all_layers()
layers

# ### Get details for an individual layer

layer = sensing_clues.get_layer_features(layer_name="test_multipolygon")

layer.head()

# #### Plot available geometries
# ### Visualize an individual layer
#
# This requires installation of library to visualize geospatial data. Here, we use Folium.

# +
# # !pip install folium
# -
# Get features for an individual and visualize it.

import folium
layer = sensing_clues.get_layer_features(layer_name="Demo_countries")
viz.plot_layer(layer)

poly_map = folium.Map([51.9244, 4.4777], zoom_start=8, tiles="cartodbpositron")
for _, geometry in layer["geometry"].items():
folium.GeoJson(geometry).add_to(poly_map)
folium.LatLngPopup().add_to(poly_map)
poly_map
viz.plot_layers(layer)

# ### Miscellaneous

# +
# You should have logged in automatically by calling the class.
# If not, you can call the login-method separately.
# status = sensing_clues.login(username, password)

# +
# It is not necessary to log out, but you can do so by calling:
# sensing_clues.logout()
Loading

0 comments on commit 53517c2

Please sign in to comment.