Skip to content

Commit

Permalink
127/tests (wildlife-dynamics#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
atmorling authored May 16, 2024
1 parent 733d3c4 commit 29f6fda
Show file tree
Hide file tree
Showing 33 changed files with 182 additions and 212 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v2

- name: Setup Micromamba
uses: mamba-org/provision-with-micromamba@main
uses: mamba-org/setup-micromamba@v1
with:
environment-file: ${{ matrix.env }}

Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ venv.bak/
#Jetbrains
.idea/

#vscode
.vscode/

# mypy
.mypy_cache/
.dmypy.json
Expand Down Expand Up @@ -162,4 +165,4 @@ tests/test_output/new.html
tests/test_output/movbank_data.csv
tests/test_output/movbank_data.csv.zip
tests/test_output/download_data.csv
tests/test_output/config.json
tests/test_output/config.json
48 changes: 14 additions & 34 deletions ecoscope/analysis/ecograph.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ def __init__(self, trajectory, resolution=15, radius=2, cutoff=None, tortuosity_
def compute(df):
subject_name = df.name
print(f"Computing EcoGraph for subject {subject_name}")
G = self._get_ecograph(self, df, subject_name, radius, cutoff, tortuosity_length)
G = self._get_ecograph(df, subject_name, radius, cutoff, tortuosity_length)
self.graphs[subject_name] = G

self.trajectory.groupby("groupby_col").progress_apply(compute)
self.trajectory.groupby("groupby_col")[self.trajectory.columns].progress_apply(compute)

def to_csv(self, output_path):
"""
Expand Down Expand Up @@ -122,13 +122,13 @@ def to_geotiff(self, feature, output_path, individual="all", interpolation=None,

if feature in self.features:
if individual == "all":
feature_ndarray = self._get_feature_mosaic(self, feature, interpolation)
feature_ndarray = self._get_feature_mosaic(feature, interpolation)
elif individual in self.graphs.keys():
feature_ndarray = self._get_feature_map(self, feature, individual, interpolation)
feature_ndarray = self._get_feature_map(feature, individual, interpolation)
else:
raise IndividualNameError("This individual is not in the dataset")
raise ValueError("This individual is not in the dataset")
else:
raise FeatureNameError("This feature was not computed by EcoGraph")
raise ValueError("This feature was not computed by EcoGraph")

if isinstance(transform, sklearn.base.TransformerMixin):
nan_mask = ~np.isnan(feature_ndarray)
Expand All @@ -151,7 +151,6 @@ def to_geotiff(self, feature, output_path, individual="all", interpolation=None,
**raster_profile,
)

@staticmethod
def _get_ecograph(self, trajectory_gdf, individual_name, radius, cutoff, tortuosity_length):
G = nx.Graph()
geom = trajectory_gdf["geometry"]
Expand All @@ -174,7 +173,7 @@ def _get_ecograph(self, trajectory_gdf, individual_name, radius, cutoff, tortuos
trajectory_gdf.iloc[i + (tortuosity_length - 1)]["segment_end"]
) - pd.to_datetime(t)
time_delta = time_diff.total_seconds() / 3600.0
tortuosity_1, tortuosity_2 = self._get_tortuosities(self, lines, time_delta)
tortuosity_1, tortuosity_2 = self._get_tortuosities(lines, time_delta)

attributes = {
"dot_product": self._get_dot_product(p1, p2, p3, p4),
Expand Down Expand Up @@ -202,7 +201,7 @@ def _get_ecograph(self, trajectory_gdf, individual_name, radius, cutoff, tortuos
else:
G.nodes[node][key] = np.nan

self._compute_network_metrics(self, G, radius, cutoff)
self._compute_network_metrics(G, radius, cutoff)
return G

@staticmethod
Expand Down Expand Up @@ -244,7 +243,6 @@ def _get_dot_product(x, y, z, w):
else:
return np.nan

@staticmethod
def _get_tortuosities(self, lines, time_delta):
point1, point2 = lines[0][0], lines[len(lines) - 1][1]
beeline_dist = np.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2)
Expand All @@ -270,18 +268,16 @@ def _get_tortuosities(self, lines, time_delta):
else:
return np.nan, np.nan

@staticmethod
def _compute_network_metrics(self, G, radius, cutoff):
self._compute_degree(G)
self._compute_betweenness(G, cutoff)
self._compute_collective_influence(self, G, radius)
self._compute_collective_influence(G, radius)

@staticmethod
def _compute_degree(G):
for node in G.nodes():
G.nodes[node]["degree"] = G.degree[node]

@staticmethod
def _compute_collective_influence(self, G, radius):
for node in G.nodes():
G.nodes[node]["collective_influence"] = self._get_collective_influence(G, node, radius)
Expand All @@ -303,11 +299,10 @@ def _compute_betweenness(G, cutoff):
node = v["_nx_name"]
G.nodes[node]["betweenness"] = btw_idx[v.index]

@staticmethod
def _get_feature_mosaic(self, feature, interpolation=None):
features = []
for individual in self.graphs.keys():
features.append(self._get_feature_map(self, feature, individual, interpolation))
features.append(self._get_feature_map(feature, individual, interpolation))
mosaic = np.full((self.n_cols, self.n_rows), np.nan)
for i in range(self.n_cols):
for j in range(self.n_rows):
Expand All @@ -322,23 +317,20 @@ def _get_feature_mosaic(self, feature, interpolation=None):
mosaic[i][j] = values[0]
return mosaic

@staticmethod
def _get_feature_map(self, feature, individual, interpolation):
if interpolation is not None:
return self._get_interpolated_feature_map(self, feature, individual, interpolation)
return self._get_interpolated_feature_map(feature, individual, interpolation)
else:
return self._get_regular_feature_map(self, feature, individual)
return self._get_regular_feature_map(feature, individual)

@staticmethod
def _get_regular_feature_map(self, feature, individual):
feature_ndarray = np.full((self.n_cols, self.n_rows), np.nan)
for node in self.graphs[individual].nodes():
feature_ndarray[node[1]][node[0]] = (self.graphs[individual]).nodes[node][feature]
return feature_ndarray

@staticmethod
def _get_interpolated_feature_map(self, feature, individual, interpolation):
feature_ndarray = self._get_regular_feature_map(self, feature, individual)
feature_ndarray = self._get_regular_feature_map(feature, individual)
individual_trajectory = self.trajectory[self.trajectory["groupby_col"] == individual]
geom = individual_trajectory["geometry"]
idxs_dict = {}
Expand Down Expand Up @@ -366,22 +358,10 @@ def _get_interpolated_feature_map(self, feature, individual, interpolation):
elif interpolation == "min":
feature_ndarray[key[0], key[1]] = np.min(value)
else:
raise InterpolationError("Interpolation type not supported by EcoGraph")
raise NotImplementedError("Interpolation type not supported by EcoGraph")
return feature_ndarray


class InterpolationError(Exception):
pass


class IndividualNameError(Exception):
pass


class FeatureNameError(Exception):
pass


def get_feature_gdf(input_path):
"""
Convert a GeoTIFF feature map into a GeoDataFrame
Expand Down
2 changes: 1 addition & 1 deletion ecoscope/analysis/proximity.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ def analysis(traj):

proximity_events.append(pr)

trajectory.groupby("groupby_col").apply(analysis)
trajectory.groupby("groupby_col")[trajectory.columns].apply(analysis, include_groups=False)
return pd.concat(proximity_events).reset_index(drop=True)
23 changes: 16 additions & 7 deletions ecoscope/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,13 @@ def apply_reloc_filter(self, fix_filter=None, inplace=False):
frame.to_crs(4326)
if isinstance(fix_filter, RelocsSpeedFilter):
frame._update_inplace(
frame.groupby("groupby_col")
frame.groupby("groupby_col")[frame.columns]
.apply(self._apply_speedfilter, fix_filter=fix_filter)
.droplevel(["groupby_col"])
)
elif isinstance(fix_filter, RelocsDistFilter):
frame._update_inplace(
frame.groupby("groupby_col")
frame.groupby("groupby_col")[frame.columns]
.apply(self._apply_distfilter, fix_filter=fix_filter)
.droplevel(["groupby_col"])
)
Expand Down Expand Up @@ -354,7 +354,7 @@ def from_relocations(cls, gdf, *args, **kwargs):
gdf = EcoDataFrame(gdf)
crs = gdf.crs
gdf.to_crs(4326, inplace=True)
gdf = gdf.groupby("groupby_col").apply(cls._create_multitraj).droplevel(level=0)
gdf = gdf.groupby("groupby_col")[gdf.columns].apply(cls._create_multitraj).droplevel(level=0)
gdf.to_crs(crs, inplace=True)
gdf.sort_values("segment_start", inplace=True)
return cls(gdf, *args, **kwargs)
Expand Down Expand Up @@ -527,7 +527,11 @@ def turn_angle(traj):
return ((traj["heading"].diff() + 540) % 360 - 180)[traj["segment_end"].shift(1) == traj["segment_start"]]

uniq = self.groupby_col.nunique()
angles = self.groupby("groupby_col").apply(turn_angle).droplevel(0) if uniq > 1 else turn_angle(self)
angles = (
self.groupby("groupby_col")[self.columns].apply(turn_angle, include_groups=False).droplevel(0)
if uniq > 1
else turn_angle(self)
)

return angles.rename("turn_angle").reindex(self.index)

Expand Down Expand Up @@ -570,7 +574,9 @@ def f(traj):
crs=traj.crs,
)

return Relocations.from_gdf(self.groupby("groupby_col").apply(f).reset_index(level=0))
return Relocations.from_gdf(
self.groupby("groupby_col")[self.columns].apply(f, include_groups=False).reset_index(level=0)
)

def to_relocations(self):
"""
Expand All @@ -595,7 +601,9 @@ def f(traj):
.sort_values("fixtime")
)

return Relocations.from_gdf(self.groupby("groupby_col").apply(f).reset_index(drop=True))
return Relocations.from_gdf(
self.groupby("groupby_col")[self.columns].apply(f, include_groups=False).reset_index(drop=True)
)

def downsample(self, freq, tolerance="0S", interpolation=False):
"""
Expand Down Expand Up @@ -651,7 +659,8 @@ def f(relocs_ind):
relocs_ind.drop(relocs_ind.loc[relocs_ind["extra__burst"] == -1].index, inplace=True)
return relocs_ind

return Relocations(self.to_relocations().groupby("groupby_col").apply(f).reset_index(drop=True))
relocs = self.to_relocations()
return relocs.groupby("groupby_col")[relocs.columns].apply(f).reset_index(drop=True)

@staticmethod
def _straighttrack_properties(df: gpd.GeoDataFrame):
Expand Down
5 changes: 3 additions & 2 deletions ecoscope/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,12 @@ def create_modis_interval_index(start, intervals, overlap=pd.Timedelta(0), close
"""

start = start + ModisBegin()
modis = ModisBegin()
start = modis.apply(start)

left = [start]
for i in range(1, intervals):
start = start + ModisBegin()
start = modis.apply(start)
left.append(start - i * overlap)
left = pd.DatetimeIndex(left)

Expand Down
8 changes: 5 additions & 3 deletions ecoscope/io/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,13 @@ def reduce_region(gdf, raster_path_list, reduce_func):
for raster_path in tqdm.tqdm(raster_path_list):
d[raster_path] = {}
with rio.open(raster_path) as src:
for i, shp in gdf.geometry.to_crs(src.crs).items():
for feat in gdf.to_crs(src.crs).iterfeatures():
try:
d[raster_path][i] = reduce_func(rio.mask.mask(src, [shp], filled=False)[0].compressed())
d[raster_path][feat["id"]] = reduce_func(
rio.mask.mask(src, [feat["geometry"]], filled=False)[0].compressed()
)
except ValueError as e:
logger.exception(raster_path, i, e)
logger.exception(raster_path, feat["id"], e)

return pd.DataFrame(d)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Read in sample data in MovBank format"
"### Read in sample data in MoveBank format"
]
},
{
Expand All @@ -104,11 +104,11 @@
"outputs": [],
"source": [
"ecoscope.io.download_file(\n",
" f\"{ECOSCOPE_RAW}/tests/sample_data/vector/movbank_data.csv\",\n",
" os.path.join(output_dir, \"movbank_data.csv\"),\n",
" f\"{ECOSCOPE_RAW}/tests/sample_data/vector/movebank_data.csv\",\n",
" os.path.join(output_dir, \"movebank_data.csv\"),\n",
")\n",
"\n",
"data = pd.read_csv(os.path.join(output_dir, \"movbank_data.csv\"))"
"data = pd.read_csv(os.path.join(output_dir, \"movebank_data.csv\"))"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@
"outputs": [],
"source": [
"ecoscope.io.download_file(\n",
" f\"{ECOSCOPE_RAW}/tests/sample_data/vector/movbank_data.csv\",\n",
" os.path.join(output_dir, \"movbank_data.csv\"),\n",
" f\"{ECOSCOPE_RAW}/tests/sample_data/vector/movebank_data.csv\",\n",
" os.path.join(output_dir, \"movebank_data.csv\"),\n",
")\n",
"\n",
"df = pd.read_csv(os.path.join(output_dir, \"movbank_data.csv\"), index_col=0)\n",
"df = pd.read_csv(os.path.join(output_dir, \"movebank_data.csv\"), index_col=0)\n",
"gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df[\"location-long\"], df[\"location-lat\"]), crs=4326)\n",
"\n",
"relocs = ecoscope.base.Relocations.from_gdf(gdf, groupby_col=\"individual-local-identifier\", time_col=\"timestamp\")\n",
Expand Down
Loading

0 comments on commit 29f6fda

Please sign in to comment.