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

Added FUSION metrics #70

Merged
merged 43 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
56d5a13
in progress: adding new metrics
bmcgaughey1 Feb 9, 2024
7001c93
in progress: fixing syntax error
bmcgaughey1 Feb 9, 2024
0d7403c
added base FUSION metrics
bmcgaughey1 Feb 9, 2024
6c0a362
comments
bmcgaughey1 Feb 9, 2024
bbdbbe5
added percentiles as separate functions
bmcgaughey1 Feb 12, 2024
97beefa
Corrected syntax metrics.py line 209
bmcgaughey1 Feb 12, 2024
43cd866
Added L-moment metrics and profile area
bmcgaughey1 Feb 13, 2024
7e25d70
Fixing a few metrics, removing errant breakpoint, adding test for met…
kylemann16 Feb 15, 2024
8afcf4e
fixing metric test, removing mode method for now
kylemann16 Feb 15, 2024
55e7f35
Merge pull request #71 from hobuinc/kmann/metrics
kylemann16 Feb 23, 2024
be8d7f9
Fixing divide by zero errors in metrics
bmcgaughey1 Mar 5, 2024
74697d3
Added simple count and check on number of points for skewness and kur…
bmcgaughey1 Mar 5, 2024
f77ff71
added htthreshhold and coverthreshold to initialize cli
bmcgaughey1 Mar 6, 2024
ca522f4
continuing with adding thresholds to metric functions
bmcgaughey1 Mar 6, 2024
064fbdb
fixing __call__
bmcgaughey1 Mar 6, 2024
0cabd0e
fixing m_mode call in m_abovemode
bmcgaughey1 Mar 6, 2024
598b641
fixing m_mode call in m_madmode
bmcgaughey1 Mar 6, 2024
32ccaf6
skipping m_madmode
bmcgaughey1 Mar 6, 2024
e54cc2d
omitting m_madmode
bmcgaughey1 Mar 6, 2024
a6c4e1f
adding use of htthreshold
bmcgaughey1 Mar 7, 2024
77d6dcb
fixing m_mode
bmcgaughey1 Mar 7, 2024
2455dac
fixing m_mode again
bmcgaughey1 Mar 7, 2024
65d119f
m_mode again
bmcgaughey1 Mar 7, 2024
ba3a69b
m_mode again
bmcgaughey1 Mar 7, 2024
a57f7bb
m_mode
bmcgaughey1 Mar 7, 2024
c88230e
dropping m_mode (temp)
bmcgaughey1 Mar 7, 2024
538a8f2
fixing typos
bmcgaughey1 Mar 7, 2024
1629cd4
backing out application of ht and cover thresholds from metrics funct…
bmcgaughey1 Mar 7, 2024
04c4e5d
exploring ht filtering
bmcgaughey1 Mar 7, 2024
0978d21
removed debug printing
bmcgaughey1 Mar 11, 2024
8e6ef2b
added example docstrings for m_mean and m_mode
bmcgaughey1 Mar 25, 2024
ecd0395
Added constants for NODATA and height thresholds
bmcgaughey1 Apr 2, 2024
8a29787
Backed out use of htthreshold and coverthreshold
bmcgaughey1 Apr 2, 2024
dd6053e
Added docstrings to all metrics functions
bmcgaughey1 Apr 2, 2024
9a6240b
Kmann/metrics (#91)
kylemann16 Sep 16, 2024
4d3cfc9
Merge remote-tracking branch 'origin/main' into metric_merge
kylemann16 Sep 16, 2024
9f0e2d5
fix merge mistake
kylemann16 Sep 16, 2024
87cdbb8
fixing merge mistake
kylemann16 Sep 16, 2024
e3e11e2
Merge remote-tracking branch 'origin/main' into metric_merge
kylemann16 Sep 16, 2024
91d300f
Revert "fixing merge mistake"
kylemann16 Sep 16, 2024
6594300
Revert "fix merge mistake"
kylemann16 Sep 16, 2024
0239aad
Revert "Merge remote-tracking branch 'origin/main' into metric_merge"
kylemann16 Sep 16, 2024
6bdf198
Merge branch 'metric_merge' into metrics_revert
kylemann16 Sep 16, 2024
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@ stats/
**/tifs_test/**
autzen-classified.copc.laz

#sample metrics
metrics/
metrics_aligned/
autzen-aligned.tdb/

.DS_Store
15 changes: 8 additions & 7 deletions docs/source/api/resources/entry.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
Entry
----------------------------------

.. autoclass:: silvimetric.resources.entry.Entry

Attribute
----------------------------------

.. autoclass:: silvimetric.resources.entry.Attribute
.. automodule:: silvimetric.resources.attribute
:members:
:undoc-members:
:show-inheritance:

Metric
----------------------------------

.. autoclass:: silvimetric.resources.metric.Metric
.. automodule:: silvimetric.resources.metric
:members:
:undoc-members:
:show-inheritance:
3 changes: 0 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
html_theme = "sphinx_rtd_theme"
html_static_path = ['_static']
html_context = {
Expand All @@ -49,8 +48,6 @@
'conf_py_path': '/docs/source/'
}

if os.environ.get("READXTHEDOCS", "") == "True":
html_context["READTHEDOCS"] = True


def read_version(filename):
Expand Down
4 changes: 1 addition & 3 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ SilviMetric is an open source library and set of utilities from
data into raster and raster-like products.

Find out more about SilviMetric by visiting :ref:`about`. A slide deck about
SilviMetric is also available on `Google Slides <https://docs.google.com/presentation/d/1E561EgWwLgN5R9P0LBxuI1r7kG155u8E6-MOWpkycSM/edit?usp=sharing>`__,
and examples are available for viewing in `Google Colab <https://colab.research.google.com/drive/1u3Qdq3Fdy2du36WG823rKVlQtBL8eK0g#scrollTo=kY0ikB6JQ2G7>`__.

SilviMetric is also available on `Google Slides <https://docs.google.com/presentation/d/1E561EgWwLgN5R9P0LBxuI1r7kG155u8E6-MOWpkycSM/edit?usp=sharing>`__.

.. toctree::
:caption: Contents
Expand Down
5 changes: 2 additions & 3 deletions docs/source/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ Example:
$ METRIC_PATH="./path/to/python_metrics.py"
$ silvimetric --database $DB_NAME initialize --bounds "$BOUNDS" \
--crs "EPSG:$EPSG" \
-m $METRIC_PATH -m min -m max -m mean
-m "${METRIC_PATH},min,max,mean"

.. warning::

Expand Down Expand Up @@ -259,8 +259,7 @@ SilviMetric will take all the previously defined variables like the bounds,
resolution, and our tile size, and it will split all data values up into their
respective bins. From here, SilviMetric will perform each `Metric` previously
defined in :ref:`initialize` over the data in each cell. At the end of all that,
this data will be written to a `SparseArray` in `TileDB`, where it will be much
easier to access.
this data will be written to a `SparseArray` in `TileDB`, where it will be much easier to access.

Usage:

Expand Down
3 changes: 2 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ dependencies:
- websocket-client
- python-json-logger
- dill
- pandas
- pandas
- lmoments3
10 changes: 6 additions & 4 deletions src/silvimetric/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from .resources.bounds import Bounds
from .resources.extents import Extents
from .resources.storage import Storage
from .resources.metric import Metric, Metrics
from .resources.metric import Metric, run_metrics
from .resources.metrics import grid_metrics, l_moments, percentiles, statistics, all_metrics
from .resources.metrics import product_moments
from .resources.log import Log
from .resources.data import Data
from .resources.entry import Attribute, Pdal_Attributes, Attributes
from .resources.config import StorageConfig, ShatterConfig, ExtractConfig, ApplicationConfig
from .resources.array_extensions import AttributeArray, AttributeDtype
from .resources.attribute import Attribute, Pdal_Attributes, Attributes
from .resources.config import StorageConfig, ShatterConfig, ExtractConfig
from .resources.config import ApplicationConfig

from .commands.shatter import shatter
from .commands.extract import extract
Expand Down
15 changes: 7 additions & 8 deletions src/silvimetric/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
@click.option("--debug", is_flag=True, default=False, help="Changes logging level from INFO to DEBUG.")
@click.option("--log-dir", default=None, help="Directory for log output", type=str)
@click.option("--progress", is_flag=True, default=True, type=bool, help="Report progress")
@click.option("--workers", type=int, default=10, help="Number of workers for Dask")
@click.option("--threads", type=int, default=4, help="Number of threads per worker for Dask")
@click.option("--workers", type=int, help="Number of workers for Dask")
@click.option("--threads", type=int, help="Number of threads per worker for Dask")
@click.option("--watch", is_flag=True, default=False, type=bool,
help="Open dask diagnostic page in default web browser.")
@click.option("--dasktype", default='processes', type=click.Choice(['threads',
'processes']), help="What Dask uses for parallelization. For more"
"information see here https://docs.dask.org/en/stable/scheduling.html#local-threads")
@click.option("--scheduler", default='distributed', type=click.Choice(['distributed',
@click.option("--scheduler", default='local', type=click.Choice(['distributed',
'local', 'single-threaded']), help="Type of dask scheduler. Both are "
"local, but are run with different dask libraries. See more here "
"https://docs.dask.org/en/stable/scheduling.html.")
Expand Down Expand Up @@ -133,14 +133,13 @@ def scan_cmd(app, resolution, point_count, pointcloud, bounds, depth, filter):
help="Coordinate system of data")
@click.option("--attributes", "-a", multiple=True, type=AttrParamType(),
help="List of attributes to include in Database")
@click.option("--metrics", "-m", multiple=True, type=MetricParamType(),
help="List of metrics to include in Database")
@click.option("--metrics", "-m", type=MetricParamType(), default=[],
help="List of metrics to include in output, eg. '-m stats,percentiles'")
@click.option("--resolution", type=float, default=30.0,
help="Summary pixel resolution")
@click.pass_obj
def initialize_cmd(app: ApplicationConfig, bounds: Bounds, crs: pyproj.CRS,
attributes: list[Attribute], resolution: float, metrics: list[Metric]):
import itertools
"""Initialize silvimetrics DATABASE"""

storageconfig = StorageConfig(tdb_dir = app.tdb_dir,
Expand Down Expand Up @@ -205,8 +204,8 @@ def shatter_cmd(app, pointcloud, bounds, report, tilesize, date, dates):
@cli.command('extract')
@click.option("--attributes", "-a", multiple=True, type=AttrParamType(), default=[],
help="List of attributes to include output")
@click.option("--metrics", "-m", multiple=True, type=MetricParamType(), default=[],
help="List of metrics to include in output")
@click.option("--metrics", "-m", type=MetricParamType(), default=[],
help="List of metrics to include in output, eg. '-m stats,percentiles'")
@click.option("--bounds", type=BoundsParamType(), default=None,
help="Bounds for data to include in output")
@click.option("--outdir", "-o", type=click.Path(exists=False), required=True,
Expand Down
101 changes: 64 additions & 37 deletions src/silvimetric/cli/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import dask
from dask.diagnostics import ProgressBar
from dask.distributed import Client, LocalCluster
from ..resources.metrics import l_moments, percentiles, statistics, product_moments
from ..resources.metrics import aad, grid_metrics, all_metrics

from .. import Bounds, Attribute, Metric, Attributes, Metrics, Log
from .. import Bounds, Attribute, Metric, Attributes, Log


class BoundsParamType(click.ParamType):
Expand All @@ -22,7 +24,7 @@ def convert(self, value, param, ctx):
class CRSParamType(click.ParamType):
name = "CRS"

def convert(self, value, param, ctx):
def convert(self, value, param, ctx) -> pyproj.CRS:
try:
crs = pyproj.CRS.from_user_input(value)
return crs
Expand All @@ -44,40 +46,68 @@ def convert(self, value, param, ctx) -> list[Attribute]:
self.fail(f"{value!r} is of an invalid type, {e}", param, ctx)

class MetricParamType(click.ParamType):
name="Metrics"
name="metrics"
def convert(self, value, param, ctx) -> list[Metric]:
if '.py' in value:
try:
import importlib.util
import os
from pathlib import Path

cwd = os.getcwd()
p = Path(cwd, value)
if not p.exists():
self.fail("Failed to find import file for metrics at"
f" {str(p)}", param, ctx)

spec = importlib.util.spec_from_file_location('user_metrics', str(p))
user_metrics = importlib.util.module_from_spec(spec)
spec.loader.exec_module(user_metrics)
ms = user_metrics.metrics()
except Exception as e:
self.fail(f"Failed to import metrics from {str(p)} with error {e}",
param, ctx)

for m in ms:
if not isinstance(m, Metric):
self.fail(f"Invalid Metric supplied: {m}")
return user_metrics.metrics()

try:
return Metrics[value]
except Exception as e:
self.fail(f"{value!r} is not available in Metrics, {e}", param, ctx)
if value is None or not value:
return list(all_metrics.values())
parsed_values = value.split(',')
metrics: set[Metric] = set()
for val in parsed_values:
if '.py' in val:
# user imported metrics from external file
try:
import importlib.util
import os
from pathlib import Path

cwd = os.getcwd()
p = Path(cwd, val)
if not p.exists():
self.fail("Failed to find import file for metrics at"
f" {str(p)}", param, ctx)

spec = importlib.util.spec_from_file_location('user_metrics', str(p))
user_metrics = importlib.util.module_from_spec(spec)
spec.loader.exec_module(user_metrics)
ms = user_metrics.metrics()
except Exception as e:
self.fail(f"Failed to import metrics from {str(p)} with error {e}",
param, ctx)

for m in ms:
if not isinstance(m, Metric):
self.fail(f"Invalid Metric supplied: {m}")

metrics.update(list(user_metrics.metrics()))
else:
# SilviMetric defined metrics
try:
if val == 'stats':
metrics.update(list(statistics.values()))
elif val == 'p_moments':
metrics.update(list(product_moments.values()))
elif val == 'l_moments':
metrics.update(list(l_moments.values()))
elif val == 'percentiles':
metrics.update(list(percentiles.values()))
elif val == 'aad':
metrics.update(list(aad.aad.values()))
elif val == 'grid_metrics':
metrics.update(list(grid_metrics.values()))
elif val == 'all':
metrics.update(list(all_metrics.values()))
else:
m = all_metrics[val]
if isinstance(m, Metric):
metrics.add(m)
else:
metrics.udpate(list(m))
except Exception as e:
self.fail(f"{val!r} is not available in Metrics", param, ctx)
return list(metrics)

def dask_handle(dasktype: str, scheduler: str, workers: int, threads: int,
watch: bool, log: Log):
watch: bool, log: Log) -> None:
dask_config = { }

if dasktype == 'threads':
Expand All @@ -88,9 +118,6 @@ def dask_handle(dasktype: str, scheduler: str, workers: int, threads: int,
dask_config['threads_per_worker'] = threads

if scheduler == 'local':
if scheduler != 'distributed':
log.warning("Selected scheduler type does not support continuously"
"updated config information.")
# fall back to dask type to determine the scheduler type
dask_config['scheduler'] = dasktype
if watch:
Expand All @@ -117,7 +144,7 @@ def dask_handle(dasktype: str, scheduler: str, workers: int, threads: int,

dask.config.set(dask_config)

def close_dask():
def close_dask() -> None:
client = dask.config.get('distributed.client')
if isinstance(client, Client):
client.close()
3 changes: 2 additions & 1 deletion src/silvimetric/commands/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from itertools import chain


from typing import Union
from osgeo import gdal, osr
import dask
import numpy as np
Expand Down Expand Up @@ -54,7 +55,7 @@ def write_tif(xsize: int, ysize: int, data:np.ndarray, name: str,
tif.FlushCache()
tif = None

def get_metrics(data_in: pd.DataFrame, storage: Storage):
def get_metrics(data_in: pd.DataFrame, storage: Storage) -> Union[None, pd.DataFrame]:
"""
Reruns a metric over this cell. Only called if there is overlapping data.

Expand Down
8 changes: 4 additions & 4 deletions src/silvimetric/commands/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ def scan(tdb_dir: str, pointcloud: str, bounds: Bounds, point_count:int=600000,
cell_counts = extent_handle(extents, data, resolution, point_count,
depth, log)


num_cells = np.sum(cell_counts).item()
std = np.std(cell_counts)
mean = np.mean(cell_counts)
rec = int(mean + std)

pc_info = dict(pc_info=dict(storage_bounds=tdb.config.root.to_json(),
data_bounds=data.bounds.to_json(), count=dask.compute(count)))
tiling_info = dict(tile_info=dict(num_cells=len(cell_counts), mean=mean,
std_dev=std, recommended=rec))
data_bounds=data.bounds.to_json(), count=dask.compute(count)))
tiling_info = dict(tile_info=dict(num_cells=num_cells,
num_tiles=len(cell_counts), mean=mean, std_dev=std, recommended=rec))

final_info = pc_info | tiling_info
logger.info(json.dumps(final_info, indent=2))
Expand Down
Loading
Loading