コード例 #1
0
 def print_custom_funs_info(self):
     """Print information about available curtom calib functions."""
     logger.info("Available polyorders (with offset): %s"
           "Available polyorders (without offset): %s"
           % (list(self.polys[0].keys()), list(self.polys[1].keys())))
     for k, v in six.iteritems(self._custom_funs_info):
         logger.info("%s : %s" % (k, v))
コード例 #2
0
    def add_retrieval_point(self, pos_x_abs, pos_y_abs, dist=None):
        """Add a distinct pixel with known distance to image.

        Parameters
        ----------
        pos_x_abs : int
            x-pixel position of point in image in absolute coordinate (i.e.
            pyramid level 0 and not cropped)
        pos_y_abs : int
            y-pixel position of point in image in absolute coordinate (i.e.
            pyramid level 0 and not cropped)
        dist : :obj:`float`, optional
            distance to feature in image in m. If None (default), the distance
            will be estimated

        """
        if not isnum(dist):
            logger.info("Input distance for point unspecified, trying automatic "
                  "access")
            (dist,
             derr,
             p) = self.meas_geometry.get_topo_distance_pix(pos_x_abs,
                                                           pos_y_abs)
            self._geopoints["add_points"].append(p)
            dist *= 1000.0
        self._add_points.append((pos_x_abs, pos_y_abs, dist))
コード例 #3
0
    def _merge_tseries_nearest(self, time_series):
        """Find nearest in time image for each time stamp in input series.

        Find indices (and time differences) in input time series of nearest
        data point for each image in this stack. Then, get rid of all indices
        showing double occurences using time delta information.
        """
        stack, time_stamps, texps = self.get_data()
        nearest_idxs, del_ts = self.get_nearest_indices(time_series.index)
        img_idxs = []
        spec_idxs_final = []
        del_ts_abs = []
        for idx in range(min(nearest_idxs), max(nearest_idxs) + 1):
            logger.info("Current tseries index %s" % idx)
            matches = where(nearest_idxs == idx)[0]
            if len(matches) > 0:
                del_ts_temp = del_ts[matches]
                spec_idxs_final.append(idx)
                del_ts_abs.append(min(del_ts_temp))
                img_idxs.append(matches[argmin(del_ts_temp)])

        series_new = time_series[spec_idxs_final]
        try:
            series_new.fit_errs = time_series.fit_errs[spec_idxs_final]
        except BaseException:
            pass
        stack_new = self.stack[img_idxs]
        texps_new = asarray(self.texps[img_idxs])
        start_acq_new = asarray(self.start_acq[img_idxs])
        stack_obj_new = ImgStack(stack_id=self.stack_id,
                                 img_prep=self.img_prep, stack=stack_new,
                                 start_acq=start_acq_new, texps=texps_new)
        stack_obj_new.roi_abs = self.roi_abs
        stack_obj_new.add_data = series_new
        return (stack_obj_new, series_new)
コード例 #4
0
    def det_topo_dists_line(self, line_id, **settings):
        """Estimate distances to pixels on current lines.

        Retrieves distances to all :class:`LineOnImage` objects  in
        ``self.lines`` using ``self.meas_geometry`` (i.e. camera position
        and viewing direction).

        Parameters
        ----------
        line_id : str
            ID of line
        **settings :
            additional key word args used to update search settings (passed to
            :func:`get_topo_distances_line` in :class:`MeasGeometry`)

        Returns
        -------
        array
            retrieved distances

        """
        if line_id not in self.lines.keys():
            raise KeyError("No line with ID %s available" % line_id)
        logger.info("Searching topo distances for pixels on line %s" % line_id)
        self.update_settings(**settings)

        l = self.lines[line_id]
        res = self.meas_geometry.get_topo_distances_line(l, **self.settings)
        dists = res["dists"] * 1000.  # convert to m
        self._geopoints[line_id] = res["geo_points"]
        self._dists_lines[line_id] = dists
        self._masks_lines[line_id] = res["ok"]
        self._skip_pix[line_id] = self.settings["skip_pix"]
        return dists
コード例 #5
0
ファイル: setupclasses.py プロジェクト: solvejgdinger/pyplis
 def update_meas_geometry(self):
     """Update the meas geometry based on current settings."""
     logger.info("Updating MeasGeometry in MeasSetup class")
     self.meas_geometry.__init__(self.source.to_dict(),
                                 self.camera.to_dict(),
                                 self.wind_info,
                                 auto_topo_access=self.auto_topo_access)
コード例 #6
0
 def calib_fun(self, val):
     if not callable(val):
         raise ValueError("Need a callable object (e.g. lambda function)")
     args = getargspec(val).args
     logger.info("Setting optimisation function in CalibData class. "
                 "Argspec: %s" % args)
     self._calib_fun = val
コード例 #7
0
ファイル: setupclasses.py プロジェクト: solvejgdinger/pyplis
    def _import_from_dict(self, info_dict):
        """Try access default information of source.

        Parameters
        ----------
        info_dict : dict
            dictonary containing source information (valid keys are all
            keys ``self._type_dict``, e.g. ``lon``, ``lat``, ``altitude``)

        Returns
        -------
        bool
            success

        """
        types = self._type_dict
        if not isinstance(info_dict, dict):
            raise TypeError(
                "need dictionary like object for source info update")
        err = []
        for key, val in six.iteritems(info_dict):
            if key in types:
                func = types[key]
            else:
                func = str
            try:
                self[key] = func(val)
            except BaseException:
                err.append(key)
        if bool(err) > 0:
            logger.info("Failed to load the following source parameters\n%s" %
                        err)

        return self.info_available
コード例 #8
0
def set_test_data_path(save_path):
    """Set local path where test data is stored."""
    if save_path.lower() in all_test_data_paths():
        logger.info("Path is already in search tree")
        return
    dirs = data_search_dirs()
    fp = join(dirs[0], "_paths.txt")
    if not exists(fp):
        fp = join(dirs[1], "_paths.txt")
    save_path = abspath(save_path)
    try:
        if not exists(save_path):
            raise IOError("Could not set test data path: specified location "
                          "does not exist: %s" % save_path)
        with open(fp, "a") as f:
            f.write("\n" + save_path + "\n")
            print_log.info("Adding new path for test data location in "
                           "file _paths.txt: %s" % save_path)
            f.close()
        if "pyplis_etna_testdata" not in listdir(save_path):
            logger.warning(
                "WARNING: test data folder (name: pyplis_etna_testdata) "
                "could not be  found at specified location, please download "
                "test data, unzip and save at: %s" % save_path)
    except:
        raise
コード例 #9
0
ファイル: utils.py プロジェクト: solvejgdinger/pyplis
    def point_in_image(self, x, y, img_array):
        """Check if a given coordinate is within image.

        Parameters
        ----------
        x : int
            x coordinate of point
        y : int
            y coordinate of point
        img_array : array
            image data

        Returns
        -------
        bool
            True if point is in image, False if not

        """
        h, w = img_array.shape[:2]
        if not 0 < x < w:
            logger.info("x coordinate out of image")
            return False
        if not 0 < y < h:
            logger.info("y coordinate out of image")
            return False
        return True
コード例 #10
0
ファイル: forms.py プロジェクト: solvejgdinger/pyplis
    def __getitem__(self, name):
        """Get item.

        Returns form with corresponding to input name (if it exists)
        """
        try:
            return self._forms[name]
        except BaseException:
            logger.info("No such form: " + str(name))
コード例 #11
0
ファイル: setupclasses.py プロジェクト: solvejgdinger/pyplis
    def check_geometry_info(self):
        """Check if all req. info for measurement geometry is available.

        Relevant parameters are:

            1. Lon, Lat of
                i. source
                #. camera

            #. Meteorology info
                i. Wind direction
                #. Wind velocity (rough estimate)

            #. Viewing direction of camera
                i. Azimuth (N)
                #. Elvation(from horizon)

            #. Alitude of camera and source

            #. Camera optics
                i. Pixel size
                #. Number of pixels detector
                #. focal length
        """
        if not isinstance(self.source, Source):
            return 0
        source = self.source
        ok = 1
        s = ("\n------------------------------\nChecking basic geometry info"
             "\n------------------------------\n")
        for key, val in six.iteritems(self.camera.geom_data):
            if not self._check_if_number(val):
                ok = 0
                s += "Missing info in Camera setup\n"
                s += self._dict_miss_info_str(key, val)
        for key in self.camera.optics_keys:
            val = self.camera[key]
            if not self._check_if_number(val):
                ok = 0
                s += self._dict_miss_info_str(key, val)
        for key, val in six.iteritems(source.geo_data):
            if not self._check_if_number(val):
                ok = 0
                s += "Missing info in Source: %s\n" % source.name
                s += self._dict_miss_info_str(key, val)
        for key, val in six.iteritems(self.wind_info):
            if not self._check_if_number(val):
                ok = 0
                s += "Missing Meteorology info\n"
                s += self._dict_miss_info_str(key, val)
        if ok:
            s += "All necessary information available\n"

        logger.info(s)

        return ok, s
コード例 #12
0
ファイル: forms.py プロジェクト: solvejgdinger/pyplis
 def update(self, x0, y0, x1, y1, id):
     """Update an existing form (or create new if it does not exist)."""
     if id in self._forms.keys():
         logger.info("Form with ID " + str(id) +
                     " could not be updated because "
                     "it does not exist, creating new form instead")
         self.remove(id)
     if self.add(x0, y0, x1, y1, id):
         return 1
     return 0
コード例 #13
0
def get_source_info_online(source_id):
    """Try to load source info from online database (@ www.ngdc.noaa.gov).

    :param str source_id: ID of source
    """
    name = source_id
    name = name.lower()
    url = ("http://www.ngdc.noaa.gov/nndc/struts/results?type_0=Like&query_0="
           "&op_8=eq&v_8=&type_10=EXACT&query_10=None+Selected&le_2=&ge_3="
           "&le_3=&ge_2=&op_5=eq&v_5=&op_6=eq&v_6=&op_7=eq&v_7=&t=102557&s=5"
           "&d=5")
    logger.info("Trying to access volcano data from URL:")
    logger.info(url)
    try:
        # it's a file like object and works just like a file
        data = urlopen(url)
    except BaseException:
        raise

    res = od()
    in_row = 0
    in_data = 0
    lc = 0
    col_num = 10
    first_volcano_name = "Abu"  # this needs to be identical
    ids = [
        "name", "country", "region", "lat", "lon", "altitude", "type",
        "status", "last_eruption"
    ]
    types = [str, str, str, float, float, float, str, str, str]
    for line in data:
        lc += 1
        if first_volcano_name in line and line.split(">")[1].\
                split("</td")[0].strip() == first_volcano_name:
            in_data, c = 1, 0
        if in_data:
            if c % col_num == 0 and name in line.lower():
                logger.info("FOUND candidate, line: ", lc)
                spl = line.split(">")[1].split("</td")[0].strip().lower()
                if name in spl:
                    logger.info("FOUND MATCH: ", spl)
                    in_row, cc = 1, 0
                    cid = spl
                    res[cid] = od()
            if in_row:
                spl = line.split(">")[1].split("</td")[0].strip()
                res[cid][ids[cc]] = types[cc](spl)
                cc += 1

            if in_row and cc == 9:
                logger.info("End of data row reached for %s" % cid)
                cc, in_row = 0, 0
            c += 1

    return res
コード例 #14
0
    def merge_with_time_series(self, time_series, method="average",
                               **kwargs):
        """High level wrapper for data merging.

        Choose from either of three methods to perform an index merging based
        on time stamps of stack and of other time series data (provided on
        input).

        Parameters
        ----------
        time_series : Series
            time series data supposed to be merged with stack data
        method : str
            merge method, currently available methods are:

                - average: determine new stack containing images averaged based
                  on start / stop time stamps of each datapoint in input
                  ``time_series`` (requires corresponding data to be available
                  in input, i.e. ``time_series`` must be of type
                  :class:`DoasResults` of ``pydoas`` library).
                - nearest: perform merging based on nearest datapoint per image
                - interpolation: perform cross interpolation onto unified time
                  index array from stack and time series data
        **kwargs
            additional keyword args specifying additional merge settings (e.g.
            ``itp_type=quadratic`` in case ``method=interpolation`` is used)

        Returns
        -------
        tuple
            2-element tuple containing

            - :obj:`ImgStack`: new stack containing merged data
            - :obj:`Series`: merged time series data

        """
        if not isinstance(time_series, Series):
            raise TypeError("Could not merge stack data with input data: "
                            "wrong type: %s" % type(time_series))

        if method == "average":
            try:
                return self._merge_tseries_average(time_series, **kwargs)
            except BaseException:
                logger.info("Failed to merge data using method average, trying "
                      "method nearest instead")
                method = "nearest"
        if method == "nearest":
            return self._merge_tseries_nearest(time_series, **kwargs)
        elif method == "interpolation":
            return self._merge_tseries_cross_interpolation(time_series,
                                                           **kwargs)
        else:
            raise TypeError("Unkown merge type: %s. Choose from "
                            "[nearest, average, interpolation]")
コード例 #15
0
    def plot(self, add_label_str="", ax=None, **kwargs):
        """Plot calibration data and fit result.

        Parameters
        ----------
        add_label_str : str
            additional string added to label of plots for legend
        ax :
            matplotlib axes object, if None, a new one is created

        """
        if "color" not in kwargs:
            kwargs["color"] = "b"

        if ax is None:
            fig, ax = subplots(1, 1, figsize=(10, 8))

        taumin, taumax = self.tau_range
        x = linspace(taumin, taumax, 100)

        cds = self.cd_vec

        ax.plot(self.tau_vec,
                cds,
                ls="",
                marker=".",
                label="Data %s" % add_label_str,
                **kwargs)
        try:
            ax.errorbar(self.tau_vec,
                        cds,
                        yerr=self.cd_vec_err,
                        marker="None",
                        ls=" ",
                        c="#b3b3b3")
        except BaseException:
            logger.warning("No CD errors available")
        try:
            cds_calib = self.calib_fun(x, *self.calib_coeffs)
            ax.plot(x,
                    cds_calib,
                    ls="-",
                    marker="",
                    label="Fit result",
                    **kwargs)
        except TypeError:
            logger.info("Calibration poly probably not fitted")

        ax.set_title("Calibration data, ID: %s" % self.calib_id)
        ax.set_ylabel(r"$S_{%s}$ [cm$^{-2}$]" % SPECIES_ID)
        ax.set_xlabel(r"$\tau_{%s}$" % self.calib_id)
        ax.grid()
        ax.legend(loc='best', fancybox=True, framealpha=0.7)
        return ax
コード例 #16
0
ファイル: utils.py プロジェクト: solvejgdinger/pyplis
 def rect_roi_rot(self):
     """Rectangle specifying coordinates of ROI aligned with line normal."""
     try:
         if not self._rect_roi_rot.shape == (5, 2):
             raise Exception
     except BaseException:
         logger.info("Rectangle for rotated ROI was not set and is not being "
               "set to default depth of +/- 30 pix around line. Use "
               "method set_rect_roi_rot to change the rectangle")
         self.set_rect_roi_rot()
     return self._rect_roi_rot
コード例 #17
0
ファイル: forms.py プロジェクト: solvejgdinger/pyplis
    def remove(self, id):
        """Remove one form from collection.

        :param str id: string id of the form to be deleted
        """
        if id not in self._forms.keys():
            logger.info("Error: could not delete form " + id + " from "
                        "collection, no such form in collection")
            return 0
        logger.info("Delete form " + id + " from collection")
        del self._forms[id]
コード例 #18
0
ファイル: forms.py プロジェクト: solvejgdinger/pyplis
    def __setitem__(self, key, val):
        """Set item method.

        Adds one form to this collection

        :param str key: string ID of new form
        :param list val: list with start / stop coords ``[x0,y0, x1, y1]``
        """
        try:
            self.add(val[0], val[1], val[2], val[3], key)
        except Exception as e:
            logger.info("Adding form failed_ %s" % repr(e))
コード例 #19
0
 def print_list_info(self):
     """Print overview information about image lists."""
     s = ("info about image lists in dataset\n-------------------------\n\n"
          "Scene image lists:\n------------------------\n")
     for lst in self.img_lists.values():
         s += ("ID: %s, type: %s, %s images\n"
               % (lst.list_id, lst.list_type, lst.nof))
     s += "\nDark image lists:\n------------------------\n"
     for lst in self.dark_lists.values():
         s += ("ID: %s, type: %s, read_gain: %s,  %s images\n"
               % (lst.list_id, lst.list_type, lst.read_gain, lst.nof))
     logger.info(s)
コード例 #20
0
    def plot_calib_fun(self,
                       add_label_str="",
                       shift_yoffset=False,
                       ax=None,
                       **kwargs):
        """Plot calibration fit result.

        Parameters
        ----------
        add_label_str : str
            additional string added to label of plots for legend
        shift_yoffset : bool
            if True, the data is plotted without y-offset
        ax :
            matplotlib axes object, if None, a new one is created

        """
        if "color" not in kwargs:
            kwargs["color"] = "b"

        if ax is None:
            fig, ax = subplots(1, 1, figsize=(10, 8))

        taumin, taumax = self.tau_range
        x = linspace(taumin, taumax, 100)
        if self.calib_coeffs is None:
            logger.warning(
                "Calibration function not yet fitted, applying default fit"
                "(1. order polynomial)")
            self.fit_calib_data()
        cds_poly = self.calib_fun(x, *self.calib_coeffs)
        if shift_yoffset:
            try:
                cds_poly -= self.y_offset
            except BaseException:
                logger.warning("Failed to subtract y offset")

        try:
            ax.plot(x,
                    cds_poly,
                    ls="-",
                    marker="",
                    label="Fit result %s" % add_label_str,
                    **kwargs)

        except TypeError:
            logger.info("Calibration poly probably not fitted")

        ax.grid()
        ax.legend(loc='best', fancybox=True, framealpha=0.7)
        return ax
コード例 #21
0
ファイル: setupclasses.py プロジェクト: solvejgdinger/pyplis
    def update_filters_from_dict(self, filter_dict):
        """Add filter objects from a dictionary.

        Parameters
        ----------
        filter_dict : dict
            dictionary, containing filter information

        """
        for f in filter_dict.values():
            if isinstance(f, Filter):
                if f.id in self._filters:
                    logger.info("Filter %s was overwritten" % f.id)
                self._filters[f.id] = f
コード例 #22
0
    def __init__(self, input=None, lst_type=ImgList, init=1):
        self.setup = None

        self.lst_type = lst_type
        self._lists_intern = od()
        self.lists_access_info = od()

        ok = self.load_input(input)

        if init and ok:
            self.init_image_lists()
        else:
            self.create_lists_default()
        logger.info("DATASET INITIALISED")
コード例 #23
0
    def set_setup(self, stp):
        """Set the current measurement setup.

        Parameters
        ----------
        stp : MeasSetup
           Class containing information about measurement setup

        """
        if isinstance(stp, MeasSetup):
            logger.info("Updating measurement setup in Dataset")
            self.setup = stp
            return 1
        return 0
コード例 #24
0
def _read_binary_timestamp(timestamp):
    u"""Read timestamp from pco camware binary format.

    This converts an (1,14)-array of pixel as given by the pco camware software
    to a valid datetime.

    Parameters
    ----------
    timestamp : array
        array containg 14 pixel which code as the following
        0 pixel 1 image counter (MSB) (00 … 99)
        1 pixel 2 image counter (00 … 99)
        2 pixel 3 image counter (00 … 99)
        3 pixel 4 image counter (LSB) (00 … 99)
        4 pixel 5 year (MSB) (20)
        5 pixel 6 year (LSB) (03 … 99)
        6 pixel 7 month (01 … 12)
        7 pixel 8 day (01 ... 31)
        8 pixel 9 h (00 … 23)
        9 pixel 10 min (00 … 59)
        10 pixel 11 s (00 … 59)
        11 pixel 12 μs * 10000 (00 … 99)
        12 pixel 13 μs * 100 (00 … 99)
        13 pixel 14 μs (00 … 90)

    Returns
    -------
    datetime.datetime
        3-element tuple containing

    """
    try:
        values = [
            10 * (timestamp[0, j] >> 4) + timestamp[0, j] -
            ((timestamp[0, j] >> 4) << 4) for j in range(14)
        ]
    except:
        try:
            values = [
                10 * (timestamp[j] >> 4) + timestamp[j] -
                ((timestamp[j] >> 4) << 4) for j in range(14)
            ]
        except:
            logger.info('Failed to convert the binary timestamp.')
    year = int(values[4] * 100 + values[5])
    microsecond = int(values[11] * 10000 + values[12] * 100 + values[13])
    endtime = datetime(year, values[6], values[7], values[8], values[9],
                       values[10], microsecond)
    return endtime
コード例 #25
0
 def __setitem__(self, key, value):
     """Update class item."""
     if key in self.__dict__:
         logger.info("Updating %s in background model" % key)
         self.__dict__[key] = value
     elif key == "mode":
         "Updating %s in background model" % key
         self.mode = value
     elif key == "surface_fit_mask":
         self.surface_fit_mask = value
     elif key == "CORR_MODE":
         logger.warning(
             "Got input key CORR_MODE which is out-dated in versions 0.10+"
             ". Updated background modelling mode accordingly")
         self.mode = value
コード例 #26
0
ファイル: setupclasses.py プロジェクト: solvejgdinger/pyplis
    def print_setup(self):
        """Print the current setup.

        Returns
        -------
        str
            print string representation

        """
        s = ("pyplis FilterSetup\n------------------------------\n"
             "All filters:\n\n")
        for flt in self._filters.values():
            s += ("%s" % flt)
        s += "Default Filter: %s\n\n" % self.default_key_on
        logger.info(s)
        return s
コード例 #27
0
 def _check_bounds(self, fun, bounds):
     """Check fit boundaries and sets inits default if necessary."""
     sd = False
     nargs = self.num_optargs_fun(fun)
     try:
         if bounds is None:
             sd = True
         elif not len(bounds) == 2:
             sd = True
         elif not (len(x) == nargs for x in bounds):
             sd = True
     except BaseException:
         sd = True
     if sd:
         logger.info("Input bounds invalid, initiating default")
         bounds = (-ones(nargs) * inf, ones(nargs) * inf)
     return bounds
コード例 #28
0
    def plot(self, include_tit=True, date_fmt=None, **kwargs):
        """Plot time series.

        Parameters
        ----------
        include_tit : bool
            Include a title
        date_fmt : str
            Date / time formatting string for x labels, passed to
            :class:`DateFormatter` instance (optional)
        **kwargs
            Additional keyword arguments passed to pandas Series plot method

        Returns
        -------
        axes
            matplotlib axes instance

        """
        try:
            self.index = self.index.to_pydatetime()
        except BaseException:
            pass
        try:
            if "style" not in kwargs:
                kwargs["style"] = "--x"

            ax = super(PixelMeanTimeSeries, self).plot(**kwargs)
            try:
                if date_fmt is not None:
                    ax.xaxis.set_major_formatter(DateFormatter(date_fmt))
            except BaseException:
                pass
            if include_tit:
                ax.set_title("Mean value (%s), roi_abs: %s"
                             % (self.name, self.roi_abs))
            ax.grid()

            return ax
        except Exception as e:
            logger.info(repr(e))
            fig, ax = subplots(1, 1)
            ax.text(.1, .1, "Plot of PixelMeanTimeSeries failed...")
            fig.canvas.draw()
            return ax
コード例 #29
0
ファイル: utils.py プロジェクト: solvejgdinger/pyplis
    def get_line_profile(self, array, order=1, **kwargs):
        """Retrieve the line profile along pixels in input array.

        Parameters
        ----------
        array : array
            2D data array (e.g. image data). Color images are converted into
            gray scale using :func:`cv2.cvtColor`.
        order : int
            order of spline interpolation used to retrieve the values along
            input coordinates (passed to :func:`map_coordinates`)
        **kwargs
            additional keword args passed to interpolation method
            :func:`map_coordinates`

        Returns
        -------
        array
            profile

        """
        try:
            array = array.img  # if input is Img object
        except BaseException:
            pass
        if ndim(array) != 2:
            if ndim(array) != 3:
                logger.info("Error retrieving line profile, invalid dimension of "
                      "input array: %s" % (ndim(array)))
                return
            if array.shape[2] != 3:
                logger.info("Error retrieving line profile, invalid dimension of "
                      "input array: %s" % (ndim(array)))
                return
            "Input in BGR, conversion into gray image"
            array = cvtColor(array, COLOR_BGR2GRAY)

        # Extract the values along the line, using interpolation
        zi = map_coordinates(array, self.profile_coords, order=order, **kwargs)
        if sum(isnan(zi)) != 0:
            logger.warning("Retrieved NaN for one or more pixels along line on input "
                 "array")
        return zi
コード例 #30
0
ファイル: utils.py プロジェクト: solvejgdinger/pyplis
    def check_coordinates(self):
        """Check line coordinates.

        Checks if coordinates are in the right order and exchanges start / stop
        points if not

        Raises
        ------
        ValueError
            if any of the current coordinates is smaller than zero

        """
        if any([x < 0 for x in self.coords]):
            raise ValueError("Invalid value encountered, all coordinates of "
                             "line must exceed zero, current coords: %s"
                             % self.coords)
        if self.start[0] > self.stop[0]:
            logger.info("x coordinate of start point is larger than of stop point: "
                  "start and stop will be exchanged")
            self.start, self.stop = self.stop, self.start