-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #51 from eEcoLiDAR/new-reader
Add command line tools
- Loading branch information
Showing
6 changed files
with
229 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Command line tools.""" | ||
|
||
|
||
class ToolException(Exception): | ||
"""Raise if a command line tool fails.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
"""Command line tool definitions.""" | ||
from __future__ import print_function | ||
|
||
import os | ||
|
||
import click | ||
from colorama import Back, init | ||
|
||
from . import ToolException | ||
from .._version import __version__ | ||
from ..select import select_above, select_below | ||
from ..spatial_selections import points_in_polygon_shp_file, points_in_polygon_wkt, points_in_polygon_wkt_file | ||
from .io import _check_save_path, _load, _save | ||
|
||
|
||
@click.group(chain=True, invoke_without_command=True) | ||
@click.version_option(version=__version__) | ||
@click.argument('input_file', nargs=1, type=click.Path(exists=True, readable=True)) | ||
@click.argument('output_file', nargs=1, type=click.Path(writable=True)) | ||
def main(input_file, output_file): | ||
"""Various commmands for selecting points from a point cloud. | ||
Note that commands can be chained. | ||
Example: | ||
laserchicken testdata/AHN3.las test.ply filter_below intensity 100 filter_above intensity 60 | ||
""" | ||
|
||
|
||
@main.resultcallback() | ||
def process_pipeline(processors, input_file, output_file): | ||
init(autoreset=True) | ||
try: | ||
_check_save_path(output_file) | ||
point_cloud = _load(input_file) | ||
for processor in processors: | ||
point_cloud = processor(point_cloud) | ||
_save(point_cloud, output_file) | ||
except ToolException as exc: | ||
print(Back.RED + "Error: " + str(exc)) | ||
|
||
|
||
@main.command('import') | ||
def _import(): | ||
"""Read data from input file and save to output file. | ||
Actually the word import can be omitted, as this is the default operation. | ||
Examples: | ||
laserchicken testdata/AHN2.las test.ply | ||
laserchicken testdata/AHN2.las test.ply import | ||
""" | ||
return lambda point_cloud: point_cloud | ||
|
||
|
||
@main.command('filter_below') | ||
@click.argument('attribute') | ||
@click.argument('threshold', type=click.FLOAT) | ||
def _filter_below(attribute, threshold): | ||
"""Select those points where the value of attribute is below threshold. | ||
Example: | ||
laserchicken testdata/AHN3.las test.ply filter_below intensity 100 | ||
""" | ||
return lambda point_cloud: select_below(point_cloud, attribute, threshold) | ||
|
||
|
||
@main.command('filter_above') | ||
@click.argument('attribute') | ||
@click.argument('threshold', type=click.FLOAT) | ||
def _filter_above(attribute, threshold): | ||
"""Select those points where the value of attribute is above threshold. | ||
Example: | ||
laserchicken testdata/AHN3.las test.ply filter_above intensity 100 | ||
""" | ||
return lambda point_cloud: select_above(point_cloud, attribute, threshold) | ||
|
||
|
||
@main.command('filter_in_polygon') | ||
@click.argument('polygon') | ||
def _filter_in_polygon(polygon): | ||
"""Select those points that are inside polygon. | ||
Polygon can be a shape file (with extension .shp or .wkt) or a WKT string. | ||
Examples: | ||
laserchicken testdata/AHN2.las test.ply filter_in_polygon testdata/ahn2_geometries_shp/ahn2_polygon.shp | ||
laserchicken testdata/AHN2.las test.ply filter_in_polygon testdata/ahn2_geometries_wkt/ahn2_polygon.wkt | ||
laserchicken testdata/AHN2.las test.ply filter_in_polygon 'POLYGON(( 243590.0 572110.0, 243640.0 572160.0, 243700.0 572110.0, 243640.0 572060.0, 243590.0 572110.0 ))' | ||
""" | ||
if os.path.isfile(polygon): | ||
ext = os.path.splitext(polygon)[1].lower() | ||
functions = { | ||
'.shp': points_in_polygon_shp_file, | ||
'.wkt': points_in_polygon_wkt_file, | ||
} | ||
if ext not in functions: | ||
raise ToolException("Unable to determine type of shapefile, " | ||
"choose from types {}".format(list(functions))) | ||
return lambda point_cloud: functions[ext](point_cloud, polygon) | ||
else: | ||
print("polygon is not a file, assuming it is a WKT string") | ||
return lambda point_cloud: points_in_polygon_wkt(point_cloud, polygon) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"""Functions for loading and saving a pointcloud from/to file.""" | ||
from __future__ import print_function | ||
|
||
import os | ||
|
||
from colorama import Fore | ||
|
||
from . import ToolException | ||
from ..read_las import read as read_las | ||
from ..read_ply import read as read_ply | ||
from ..write_ply import write as write_ply | ||
|
||
READERS = { | ||
'.ply': read_ply, | ||
'.las': read_las, | ||
} | ||
|
||
WRITERS = { | ||
'.ply': write_ply, | ||
} | ||
|
||
|
||
def _load(filename, reader=None): | ||
"""Load point cloud from filename using reader.""" | ||
print("Reading the input file", end='') | ||
|
||
if reader is None: | ||
ext = os.path.splitext(filename)[1].lower() | ||
if ext not in READERS: | ||
raise ToolException("Unable to guess the file type from the extension {}, " | ||
"please specify a reader argument".format(ext)) | ||
reader = READERS[ext] | ||
|
||
point_cloud = reader(filename) | ||
print(Fore.GREEN + " [DONE]") | ||
return point_cloud | ||
|
||
|
||
def _check_save_path(filename): | ||
"""Check that filename can be used to save the point cloud.""" | ||
ext = os.path.splitext(filename)[1].lower() | ||
if ext not in WRITERS: | ||
ToolException("Unknown output file format {}, choose from {}".format(ext, list(WRITERS))) | ||
|
||
if os.path.exists(filename): | ||
raise ToolException("Output file already exists! --> {}".format(filename)) | ||
|
||
output_directory = os.path.dirname(filename) | ||
|
||
if output_directory and not os.path.exists(output_directory): | ||
raise ToolException("Output file path does not exist! --> {}".format(output_directory)) | ||
|
||
|
||
def _save(point_cloud, filename, writer=None): | ||
"""Save point cloud to filename using writer.""" | ||
_check_save_path(filename) | ||
print("File will be saved as {}".format(filename)) | ||
|
||
if writer is None: | ||
ext = os.path.splitext(filename)[1].lower() | ||
if ext not in WRITERS: | ||
raise ToolException("Unable to guess the file type from the extension {}, " | ||
"please specify a saver argument".format(ext)) | ||
writer = WRITERS[ext] | ||
|
||
print("Saving, please wait...", end='') | ||
try: | ||
writer(point_cloud, filename) | ||
except Exception as exc: | ||
print(Fore.RED + " [ERROR]") | ||
print("An exception of type {} occurred. Arguments:\n{!r}".format(type(exc).__name__, exc.args)) | ||
raise ToolException("Conversion has failed! \nCheck '{}' in laserchicken module.".format(writer)) | ||
|
||
print(Fore.GREEN + " [DONE]") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import pytest | ||
from click.testing import CliRunner | ||
from . import cli | ||
|
||
|
||
@pytest.fixture | ||
def runner(): | ||
return CliRunner() | ||
|
||
|
||
# def test_cli(runner): | ||
# result = runner.invoke(cli.main) | ||
# assert result.exit_code == 0 | ||
# assert not result.exception | ||
# assert result.output.strip() == 'Hello.' | ||
# | ||
# | ||
# def test_cli_with_option(runner): | ||
# result = runner.invoke(cli.main, ['--', './testdata/AHN2.las']) | ||
# assert not result.exception | ||
# assert result.exit_code == 0 | ||
# assert result.output.strip() == '' | ||
# | ||
# | ||
# def test_cli_with_arg(runner): | ||
# result = runner.invoke(cli.main, ['Chicken']) | ||
# assert result.exit_code == 0 | ||
# assert not result.exception | ||
# assert result.output.strip() == '' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,5 @@ python-dateutil | |
shapely | ||
PyShp | ||
pandas | ||
click | ||
colorama |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters