From 1bfbdba2d8fdf326b69844e62dc91cf81600cbb4 Mon Sep 17 00:00:00 2001 From: David Hoese Date: Fri, 8 Sep 2017 09:45:49 -0500 Subject: [PATCH] Change writer configs from INI (.cfg) to YAML (#63) * Change writer configs from INI (.cfg) to YAML * Add very simple writer tests and fix writer load from Scene --- .travis.yml | 8 ++- satpy/etc/writers/cf.cfg | 7 --- satpy/etc/writers/cf.yaml | 7 +++ satpy/etc/writers/geotiff.cfg | 9 ---- satpy/etc/writers/geotiff.yaml | 7 +++ satpy/etc/writers/ninjotiff.cfg | 7 --- satpy/etc/writers/ninjotiff.yaml | 7 +++ satpy/etc/writers/simple_image.cfg | 5 -- satpy/etc/writers/simple_image.yaml | 5 ++ satpy/plugin_base.py | 37 ++++--------- satpy/scene.py | 29 +++++------ satpy/tests/__init__.py | 3 +- satpy/tests/writer_tests/__init__.py | 43 +++++++++++++++ satpy/tests/writer_tests/test_cf.py | 52 +++++++++++++++++++ satpy/tests/writer_tests/test_geotiff.py | 52 +++++++++++++++++++ satpy/tests/writer_tests/test_ninjotiff.py | 52 +++++++++++++++++++ satpy/tests/writer_tests/test_simple_image.py | 52 +++++++++++++++++++ satpy/writers/__init__.py | 14 +++-- satpy/writers/geotiff.py | 12 ++--- satpy/writers/ninjotiff.py | 6 +-- satpy/writers/simple_image.py | 2 +- travis/linux_install.sh | 13 +++++ 22 files changed, 336 insertions(+), 93 deletions(-) delete mode 100644 satpy/etc/writers/cf.cfg create mode 100644 satpy/etc/writers/cf.yaml delete mode 100644 satpy/etc/writers/geotiff.cfg create mode 100644 satpy/etc/writers/geotiff.yaml delete mode 100644 satpy/etc/writers/ninjotiff.cfg create mode 100644 satpy/etc/writers/ninjotiff.yaml delete mode 100644 satpy/etc/writers/simple_image.cfg create mode 100644 satpy/etc/writers/simple_image.yaml create mode 100644 satpy/tests/writer_tests/__init__.py create mode 100644 satpy/tests/writer_tests/test_cf.py create mode 100644 satpy/tests/writer_tests/test_geotiff.py create mode 100644 satpy/tests/writer_tests/test_ninjotiff.py create mode 100644 satpy/tests/writer_tests/test_simple_image.py create mode 100644 travis/linux_install.sh diff --git a/.travis.yml b/.travis.yml index 5185883fbc..4aa6ea2d56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ python: - '3.6' os: - linux +before_install: +- source travis/linux_install.sh install: - pip install -U pip - pip install -U setuptools @@ -18,10 +20,12 @@ install: - pip install h5netcdf - pip install python-hdf4 - "pip install --no-binary :all: netCDF4" +- pip install gdal==1.10.0 addons: apt_packages: + - libgdal-dev - libhdf5-serial-dev - - libhdf4-dev + - libhdf4-alt-dev - netcdf-bin - libnetcdf-dev - cython @@ -37,7 +41,7 @@ deploy: on: tags: true repo: pytroll/satpy -sudo: false +sudo: true notifications: slack: rooms: diff --git a/satpy/etc/writers/cf.cfg b/satpy/etc/writers/cf.cfg deleted file mode 100644 index 880b014d2e..0000000000 --- a/satpy/etc/writers/cf.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[writer:cf] -name=cf -description=Generic netCDF4/CF Writer -writer=satpy.writers.cf_writer.CFWriter -file_pattern={name}_{start_time:%Y%m%d_%H%M%S}.nc -compress=DEFLATE -zlevel=6 diff --git a/satpy/etc/writers/cf.yaml b/satpy/etc/writers/cf.yaml new file mode 100644 index 0000000000..0dfd3ae5cb --- /dev/null +++ b/satpy/etc/writers/cf.yaml @@ -0,0 +1,7 @@ +writer: + name: cf + description: Generic netCDF4/CF Writer + writer: !!python/name:satpy.writers.cf_writer.CFWriter + file_pattern: '{name}_{start_time:%Y%m%d_%H%M%S}.nc' + compress: DEFLATE + zlevel: 6 diff --git a/satpy/etc/writers/geotiff.cfg b/satpy/etc/writers/geotiff.cfg deleted file mode 100644 index de67980ae4..0000000000 --- a/satpy/etc/writers/geotiff.cfg +++ /dev/null @@ -1,9 +0,0 @@ -# Default geotiff writer - -[writer:geotiff] -name=geotiff -description=Generic GeoTIFF Writer -writer=satpy.writers.geotiff.GeoTIFFWriter -file_pattern={name}_{start_time:%Y%m%d_%H%M%S}.tif -compress=DEFLATE -zlevel=6 \ No newline at end of file diff --git a/satpy/etc/writers/geotiff.yaml b/satpy/etc/writers/geotiff.yaml new file mode 100644 index 0000000000..36ba358bbe --- /dev/null +++ b/satpy/etc/writers/geotiff.yaml @@ -0,0 +1,7 @@ +writer: + name: geotiff + description: Generic GeoTIFF Writer + writer: !!python/name:satpy.writers.geotiff.GeoTIFFWriter + file_pattern: '{name}_{start_time:%Y%m%d_%H%M%S}.tif' + compress: DEFLATE + zlevel: 6 \ No newline at end of file diff --git a/satpy/etc/writers/ninjotiff.cfg b/satpy/etc/writers/ninjotiff.cfg deleted file mode 100644 index c4b3c09f8f..0000000000 --- a/satpy/etc/writers/ninjotiff.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[writer:ninjotiff] -name=ninjotiff -description=NinjoTIFF Writer -writer=satpy.writers.ninjotiff.NinjoTIFFWriter -file_pattern={name}_{start_time:%Y%m%d_%H%M%S}.tif -compress=DEFLATE -zlevel=6 diff --git a/satpy/etc/writers/ninjotiff.yaml b/satpy/etc/writers/ninjotiff.yaml new file mode 100644 index 0000000000..6824a6a3a4 --- /dev/null +++ b/satpy/etc/writers/ninjotiff.yaml @@ -0,0 +1,7 @@ +writer: + name: ninjotiff + description: NinjoTIFF Writer + writer: !!python/name:satpy.writers.ninjotiff.NinjoTIFFWriter + file_pattern: '{name}_{start_time:%Y%m%d_%H%M%S}.tif' + compress: DEFLATE + zlevel: 6 diff --git a/satpy/etc/writers/simple_image.cfg b/satpy/etc/writers/simple_image.cfg deleted file mode 100644 index fdff2127c0..0000000000 --- a/satpy/etc/writers/simple_image.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[writer:simple_image] -name=simple_image -description=Generic Image Writer -writer=satpy.writers.simple_image.PillowWriter -file_pattern={name}_{start_time:%Y%m%d_%H%M%S}.png diff --git a/satpy/etc/writers/simple_image.yaml b/satpy/etc/writers/simple_image.yaml new file mode 100644 index 0000000000..e96c3ec74a --- /dev/null +++ b/satpy/etc/writers/simple_image.yaml @@ -0,0 +1,5 @@ +writer: + name: simple_image + description: Generic Image Writer + writer: !!python/name:satpy.writers.simple_image.PillowWriter + file_pattern: '{name}_{start_time:%Y%m%d_%H%M%S}.png' diff --git a/satpy/plugin_base.py b/satpy/plugin_base.py index 86d783eb2e..86a9cac40d 100644 --- a/satpy/plugin_base.py +++ b/satpy/plugin_base.py @@ -24,12 +24,13 @@ import logging import os +import yaml -from satpy.config import config_search_paths, get_environ_config_dir +from satpy.config import config_search_paths, get_environ_config_dir, recursive_dict_update try: import configparser -except: +except ImportError: from six.moves import configparser LOG = logging.getLogger(__name__) @@ -56,29 +57,11 @@ def __init__(self, if not isinstance(self.config_files, (list, tuple)): self.config_files = [self.config_files] + self.config = {} if self.config_files: - conf = configparser.RawConfigParser() - conf.read(self.config_files) - self.load_config(conf) - - # FIXME: why is this a static method, and not a function ? - @staticmethod - def _runtime_import(object_path): - """Import at runtime - """ - obj_module, obj_element = object_path.rsplit(".", 1) - loader = __import__(obj_module, globals(), locals(), [obj_element]) - return getattr(loader, obj_element) - - def get_section_type(self, section_name): - return section_name.split(":")[0] - - def load_config(self, conf): - # XXX: Need to load specific object section first if we want to do name-based section filtering - # Assumes only one section with "reader:" prefix - for section_name in conf.sections(): - section_type = self.get_section_type(section_name) - load_func = "load_section_%s" % (section_type, ) - if hasattr(self, load_func): - getattr(self, load_func)(section_name, - dict(conf.items(section_name))) + for config_file in self.config_files: + self.load_yaml_config(config_file) + + def load_yaml_config(self, conf): + with open(conf) as fd: + self.config = recursive_dict_update(self.config, yaml.load(fd)) diff --git a/satpy/scene.py b/satpy/scene.py index 6b9c88bae5..091b31b1c7 100644 --- a/satpy/scene.py +++ b/satpy/scene.py @@ -26,10 +26,11 @@ import logging import os +import yaml from satpy.composites import CompositorLoader, IncompatibleAreas from satpy.config import (config_search_paths, get_environ_config_dir, - runtime_import) + runtime_import, recursive_dict_update) from satpy.dataset import Dataset, DatasetID, InfoObject from satpy.node import DependencyTree from satpy.readers import DatasetDict, ReaderFinder @@ -595,21 +596,15 @@ def images(self): yield projectable.to_image() def load_writer_config(self, config_files, **kwargs): - conf = configparser.RawConfigParser() - successes = conf.read(config_files) - if not successes: - raise IOError("Writer configuration files do not exist: %s" % - (config_files, )) - - for section_name in conf.sections(): - if section_name.startswith("writer:"): - options = dict(conf.items(section_name)) - writer_class_name = options["writer"] - writer_class = runtime_import(writer_class_name) - writer = writer_class(ppp_config_dir=self.ppp_config_dir, - config_files=config_files, - **kwargs) - return writer + conf = {} + for conf_fn in config_files: + with open(conf_fn) as fd: + conf = recursive_dict_update(conf, yaml.load(fd)) + writer_class = conf['writer']['writer'] + writer = writer_class(ppp_config_dir=self.ppp_config_dir, + config_files=config_files, + **kwargs) + return writer def save_dataset(self, dataset_id, filename=None, writer=None, overlay=None, **kwargs): """Save the *dataset_id* to file using *writer* (geotiff by default). @@ -637,7 +632,7 @@ def save_datasets(self, writer="geotiff", datasets=None, **kwargs): writer.save_datasets(datasets, **kwargs) def get_writer(self, writer="geotiff", **kwargs): - config_fn = writer + ".cfg" if "." not in writer else writer + config_fn = writer + ".yaml" if "." not in writer else writer config_files = config_search_paths( os.path.join("writers", config_fn), self.ppp_config_dir) kwargs.setdefault("config_files", config_files) diff --git a/satpy/tests/__init__.py b/satpy/tests/__init__.py index 0f48a4f132..cc89d30419 100644 --- a/satpy/tests/__init__.py +++ b/satpy/tests/__init__.py @@ -28,7 +28,7 @@ from satpy.tests import (reader_tests, test_dataset, test_file_handlers, test_helper_functions, test_readers, test_resample, test_scene, test_utils, test_writers, - test_yaml_reader) + test_yaml_reader, writer_tests) if sys.version_info < (2, 7): import unittest2 as unittest @@ -50,6 +50,7 @@ def suite(): mysuite.addTests(test_yaml_reader.suite()) mysuite.addTests(test_helper_functions.suite()) mysuite.addTests(reader_tests.suite()) + mysuite.addTests(writer_tests.suite()) mysuite.addTests(test_file_handlers.suite()) mysuite.addTests(test_utils.suite()) diff --git a/satpy/tests/writer_tests/__init__.py b/satpy/tests/writer_tests/__init__.py new file mode 100644 index 0000000000..565fc8b151 --- /dev/null +++ b/satpy/tests/writer_tests/__init__.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 David Hoese +# +# Author(s): +# +# David Hoese +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""The writer tests package. +""" + +import sys + +from satpy.tests.writer_tests import (test_cf, test_geotiff, + test_ninjotiff, test_simple_image) + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +def suite(): + """Test suite for all writer tests""" + mysuite = unittest.TestSuite() + # mysuite.addTests(test_cf.suite()) + mysuite.addTests(test_geotiff.suite()) + # mysuite.addTests(test_ninjotiff.suite()) + mysuite.addTests(test_simple_image.suite()) + return mysuite diff --git a/satpy/tests/writer_tests/test_cf.py b/satpy/tests/writer_tests/test_cf.py new file mode 100644 index 0000000000..f76516c4be --- /dev/null +++ b/satpy/tests/writer_tests/test_cf.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 David Hoese +# +# Author(s): +# +# David Hoese +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for the CF writer. +""" +import os +import sys + +import numpy as np + +try: + from unittest import mock +except ImportError: + import mock + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class TestCFWriter(unittest.TestCase): + def test_init(self): + from satpy.writers.cf_writer import CFWriter + w = CFWriter() + + +def suite(): + """The test suite for this writer's tests. + """ + loader = unittest.TestLoader() + mysuite = unittest.TestSuite() + mysuite.addTest(loader.loadTestsFromTestCase(TestCFWriter)) + return mysuite diff --git a/satpy/tests/writer_tests/test_geotiff.py b/satpy/tests/writer_tests/test_geotiff.py new file mode 100644 index 0000000000..4fa8c2c41f --- /dev/null +++ b/satpy/tests/writer_tests/test_geotiff.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 David Hoese +# +# Author(s): +# +# David Hoese +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for the CF writer. +""" +import os +import sys + +import numpy as np + +try: + from unittest import mock +except ImportError: + import mock + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class TestGeoTIFFWriter(unittest.TestCase): + def test_init(self): + from satpy.writers.geotiff import GeoTIFFWriter + w = GeoTIFFWriter() + + +def suite(): + """The test suite for this writer's tests. + """ + loader = unittest.TestLoader() + mysuite = unittest.TestSuite() + mysuite.addTest(loader.loadTestsFromTestCase(TestGeoTIFFWriter)) + return mysuite diff --git a/satpy/tests/writer_tests/test_ninjotiff.py b/satpy/tests/writer_tests/test_ninjotiff.py new file mode 100644 index 0000000000..23ff9d1430 --- /dev/null +++ b/satpy/tests/writer_tests/test_ninjotiff.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 David Hoese +# +# Author(s): +# +# David Hoese +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for the CF writer. +""" +import os +import sys + +import numpy as np + +try: + from unittest import mock +except ImportError: + import mock + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class TestNinjoTIFFWriter(unittest.TestCase): + def test_init(self): + from satpy.writers.ninjotiff import NinjoTIFFWriter + w = NinjoTIFFWriter() + + +def suite(): + """The test suite for this writer's tests. + """ + loader = unittest.TestLoader() + mysuite = unittest.TestSuite() + mysuite.addTest(loader.loadTestsFromTestCase(TestNinjoTIFFWriter)) + return mysuite diff --git a/satpy/tests/writer_tests/test_simple_image.py b/satpy/tests/writer_tests/test_simple_image.py new file mode 100644 index 0000000000..a66c74d93f --- /dev/null +++ b/satpy/tests/writer_tests/test_simple_image.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 David Hoese +# +# Author(s): +# +# David Hoese +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for the CF writer. +""" +import os +import sys + +import numpy as np + +try: + from unittest import mock +except ImportError: + import mock + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class TestPillowWriter(unittest.TestCase): + def test_init(self): + from satpy.writers.simple_image import PillowWriter + w = PillowWriter() + + +def suite(): + """The test suite for this writer's tests. + """ + loader = unittest.TestLoader() + mysuite = unittest.TestSuite() + mysuite.addTest(loader.loadTestsFromTestCase(TestPillowWriter)) + return mysuite diff --git a/satpy/writers/__init__.py b/satpy/writers/__init__.py index 2107b02fa3..4952a28b91 100644 --- a/satpy/writers/__init__.py +++ b/satpy/writers/__init__.py @@ -193,13 +193,14 @@ def __init__(self, **kwargs): # Load the config Plugin.__init__(self, **kwargs) + self.info = self.config['writer'] # Use options from the config file if they weren't passed as arguments - self.name = self.config_options.get("name", - None) if name is None else name - self.fill_value = self.config_options.get( + self.name = self.info.get("name", + None) if name is None else name + self.fill_value = self.info.get( "fill_value", None) if fill_value is None else fill_value - self.file_pattern = self.config_options.get( + self.file_pattern = self.info.get( "file_pattern", None) if file_pattern is None else file_pattern if self.name is None: @@ -219,9 +220,6 @@ def create_filename_parser(self, base_dir): self.filename_parser = parser.Parser( file_pattern) if file_pattern else None - def load_section_writer(self, section_name, section_options): - self.config_options = section_options - def get_filename(self, **kwargs): if self.filename_parser is None: raise RuntimeError( @@ -256,7 +254,7 @@ def __init__(self, **kwargs): Writer.__init__(self, name, fill_value, file_pattern, base_dir, **kwargs) - enhancement_config = self.config_options.get( + enhancement_config = self.info.get( "enhancement_config", None) if enhancement_config is None else enhancement_config diff --git a/satpy/writers/geotiff.py b/satpy/writers/geotiff.py index 13be242932..d1332ad734 100644 --- a/satpy/writers/geotiff.py +++ b/satpy/writers/geotiff.py @@ -60,14 +60,14 @@ class GeoTIFFWriter(ImageWriter): def __init__(self, floating_point=False, tags=None, **kwargs): ImageWriter.__init__(self, - default_config_filename="writers/geotiff.cfg", + default_config_filename="writers/geotiff.yaml", **kwargs) - self.floating_point = bool(self.config_options.get( + self.floating_point = bool(self.info.get( "floating_point", None) if floating_point is None else floating_point) - self.tags = self.config_options.get("tags", - None) if tags is None else tags + self.tags = self.info.get("tags", + None) if tags is None else tags if self.tags is None: self.tags = {} elif not isinstance(self.tags, dict): @@ -77,8 +77,8 @@ def __init__(self, floating_point=False, tags=None, **kwargs): # GDAL specific settings self.gdal_options = {} for k in self.GDAL_OPTIONS: - if k in kwargs or k in self.config_options: - self.gdal_options[k] = kwargs.get(k, self.config_options[k]) + if k in kwargs or k in self.info: + self.gdal_options[k] = kwargs.get(k, self.info[k]) def _gdal_write_datasets(self, dst_ds, datasets, opacity, fill_value): """Write *datasets* in a gdal raster structure *dts_ds*, using diff --git a/satpy/writers/ninjotiff.py b/satpy/writers/ninjotiff.py index 6d803a84cf..5223a5cf1b 100644 --- a/satpy/writers/ninjotiff.py +++ b/satpy/writers/ninjotiff.py @@ -62,14 +62,14 @@ class NinjoTIFFWriter(ImageWriter): def __init__(self, floating_point=False, tags=None, **kwargs): ImageWriter.__init__(self, - default_config_filename="writers/ninjotiff.cfg", + default_config_filename="writers/ninjotiff.yaml", **kwargs) # self.floating_point = bool(self.config_options.get( # "floating_point", None) if floating_point is None else # floating_point) - self.tags = self.config_options.get("tags", - None) if tags is None else tags + self.tags = self.info.get("tags", + None) if tags is None else tags if self.tags is None: self.tags = {} elif not isinstance(self.tags, dict): diff --git a/satpy/writers/simple_image.py b/satpy/writers/simple_image.py index 9900e1315d..ceed942487 100644 --- a/satpy/writers/simple_image.py +++ b/satpy/writers/simple_image.py @@ -32,7 +32,7 @@ class PillowWriter(ImageWriter): def __init__(self, **kwargs): ImageWriter.__init__( self, - default_config_filename="writers/simple_image.cfg", + default_config_filename="writers/simple_image.yaml", **kwargs) def save_image(self, img, filename=None, **kwargs): diff --git a/travis/linux_install.sh b/travis/linux_install.sh new file mode 100644 index 0000000000..a1f4c6be5f --- /dev/null +++ b/travis/linux_install.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -ex + +# needed to build Python binding for GDAL +export CPLUS_INCLUDE_PATH=/usr/include/gdal +export C_INCLUDE_PATH=/usr/include/gdal +# the libhdf4-alt-dev package puts libraries in a weird place +# it says so that it doesn't conflict with netcdf...let's see what happens +sudo ln -s libmfhdfalt.so /usr/lib/libmfhdf.so +sudo ln -s libdfalt.so /usr/lib/libdf.so + +set +ex \ No newline at end of file