Skip to content
Snippets Groups Projects
Commit bafd4845 authored by Frisinghelli Daniel's avatar Frisinghelli Daniel
Browse files

Added docstrings: part 2

parent 90c93b50
No related branches found
No related tags found
No related merge requests found
...@@ -47,7 +47,8 @@ class Conv2dSame(nn.Conv2d): ...@@ -47,7 +47,8 @@ class Conv2dSame(nn.Conv2d):
self.padding = (y_pad, x_pad) self.padding = (y_pad, x_pad)
def same_padding(self, d, k): @staticmethod
def same_padding(d, k):
"""Calculate the amount of padding. """Calculate the amount of padding.
Parameters Parameters
......
"""Logging configuration."""
# !/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Created on Fri Aug 14 10:07:12 2020
@author: Daniel
"""
# builtins # builtins
import pathlib import pathlib
......
"""A collection of neural networks for semantic image segmentation.""" """Neural networks for semantic image segmentation."""
# !/usr/bin/env python # !/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
......
"""A collection of functions for model inference.""" """Functions for model inference."""
# !/usr/bin/env python # !/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
......
# -*- coding: utf-8 -*- """Data augmentation.
"""
Created on Fri Jul 17 15:28:18 2020
@author: Daniel Image transformations to artificially increase a dataset.
""" """
# !/usr/bin/env python
# -*- coding: utf-8 -*-
# externals # externals
import numpy as np import numpy as np
from scipy import ndimage from scipy import ndimage
class Transform(object): class Transform(object):
"""Base class for an image transformation."""
def __init__(self): def __init__(self):
pass pass
def __call__(self, image): def __call__(self, image):
"""Apply transformation.
Parameters
----------
image : `numpy.ndarray`
The image to transform.
Raises
------
NotImplementedError
Raised if `~pysegcnn.core.transforms.Transform` is not inherited.
Returns
-------
None.
"""
raise NotImplementedError raise NotImplementedError
class VariantTransform(Transform): class VariantTransform(Transform):
"""Base class for a spatially variant transformation.
Transformation on the ground truth required.
"""
def __init__(self): def __init__(self):
# requires transformation on the ground truth # transformation on the ground truth required
self.invariant = False self.invariant = False
class InvariantTransform(Transform): class InvariantTransform(Transform):
"""Base class for a spatially invariant transformation.
Transformation on the ground truth not required.
"""
def __init__(self): def __init__(self):
...@@ -36,6 +63,18 @@ class InvariantTransform(Transform): ...@@ -36,6 +63,18 @@ class InvariantTransform(Transform):
class FlipLr(VariantTransform): class FlipLr(VariantTransform):
"""Flip an image horizontally.
Parameters
----------
p : `float`, optional
The probability to apply the transformation. The default is 0.5.
Returns
-------
None.
"""
def __init__(self, p=0.5): def __init__(self, p=0.5):
super().__init__() super().__init__()
...@@ -43,6 +82,19 @@ class FlipLr(VariantTransform): ...@@ -43,6 +82,19 @@ class FlipLr(VariantTransform):
self.p = p self.p = p
def __call__(self, image): def __call__(self, image):
"""Apply transformation.
Parameters
----------
image : `numpy.ndarray`
The image to transform.
Returns
-------
transform : `numpy.ndarray`
The transformed image.
"""
if np.random.random(1) < self.p: if np.random.random(1) < self.p:
# transformation applied # transformation applied
self.applied = True self.applied = True
...@@ -53,10 +105,30 @@ class FlipLr(VariantTransform): ...@@ -53,10 +105,30 @@ class FlipLr(VariantTransform):
return np.asarray(image) return np.asarray(image)
def __repr__(self): def __repr__(self):
"""Representation of the transformation.
Returns
-------
repr : `str`
Representation string.
"""
return self.__class__.__name__ + '(p = {})'.format(self.p) return self.__class__.__name__ + '(p = {})'.format(self.p)
class FlipUd(VariantTransform): class FlipUd(VariantTransform):
"""Flip an image vertically.
Parameters
----------
p : `float`, optional
The probability to apply the transformation. The default is 0.5.
Returns
-------
None.
"""
def __init__(self, p=0.5): def __init__(self, p=0.5):
super().__init__() super().__init__()
...@@ -64,6 +136,19 @@ class FlipUd(VariantTransform): ...@@ -64,6 +136,19 @@ class FlipUd(VariantTransform):
self.p = p self.p = p
def __call__(self, image): def __call__(self, image):
"""Apply transformation.
Parameters
----------
image : `numpy.ndarray`
The image to transform.
Returns
-------
transform : `numpy.ndarray`
The transformed image.
"""
if np.random.random(1) < self.p: if np.random.random(1) < self.p:
# transformation applied # transformation applied
self.applied = True self.applied = True
...@@ -74,10 +159,36 @@ class FlipUd(VariantTransform): ...@@ -74,10 +159,36 @@ class FlipUd(VariantTransform):
return np.asarray(image) return np.asarray(image)
def __repr__(self): def __repr__(self):
"""Representation of the transformation.
Returns
-------
repr : `str`
Representation string.
"""
return self.__class__.__name__ + '(p = {})'.format(self.p) return self.__class__.__name__ + '(p = {})'.format(self.p)
class Rotate(VariantTransform): class Rotate(VariantTransform):
"""Rotate an image by ``angle``.
The image is rotated in the spatial plane.
If the input array has more then two dimensions, the spatial dimensions are
assumed to be the last two dimensions of the array.
Parameters
----------
angle : `float`
The rotation angle in degrees.
p : `float`, optional
The probability to apply the transformation. The default is 0.5.
Returns
-------
None.
"""
def __init__(self, angle, p=0.5): def __init__(self, angle, p=0.5):
super().__init__() super().__init__()
...@@ -89,7 +200,18 @@ class Rotate(VariantTransform): ...@@ -89,7 +200,18 @@ class Rotate(VariantTransform):
self.p = p self.p = p
def __call__(self, image): def __call__(self, image):
"""Apply transformation.
Parameters
----------
image : `numpy.ndarray`
The image to transform.
Returns
-------
transform : `numpy.ndarray`
The transformed image.
"""
if np.random.random(1) < self.p: if np.random.random(1) < self.p:
# transformation applied # transformation applied
...@@ -111,19 +233,63 @@ class Rotate(VariantTransform): ...@@ -111,19 +233,63 @@ class Rotate(VariantTransform):
return np.asarray(image) return np.asarray(image)
def __repr__(self): def __repr__(self):
"""Representation of the transformation.
Returns
-------
repr : `str`
Representation string.
"""
return self.__class__.__name__ + '(angle = {}, p = {})'.format( return self.__class__.__name__ + '(angle = {}, p = {})'.format(
self.angle, self.p) self.angle, self.p)
class Noise(InvariantTransform): class Noise(InvariantTransform):
"""Add gaussian noise to an image.
def __init__(self, mode, mean=0, var=0.05, p=0.5, exclude=0):
Valid modes are:
'add': image = image + noise
'speckle' : image = image + image * noise
Parameters
----------
mode : `str`
The mode to add the noise.
mean : `float`, optional
The mean of the gaussian distribution from which the noise is sampled.
The default is 0.
var : `float`, optional
The variance of the gaussian distribution from which the noise is
sampled. The default is 0.05.
p : `float`, optional
The probability to apply the transformation. The default is 0.5.
exclude : `list` [`float`] or `list` [`int`], optional
Values for which the noise is not added. Useful for pixels resulting
from image padding. The default is [].
Raises
------
ValueError
Raised if ``mode`` is not supported.
Returns
-------
None.
"""
# supported modes
modes = ['add', 'speckle']
def __init__(self, mode, mean=0, var=0.05, p=0.5, exclude=[]):
super().__init__() super().__init__()
# check which kind of noise to apply # check which kind of noise to apply
modes = ['gaussian', 'speckle'] if mode not in self.modes:
if mode not in modes: raise ValueError('Supported noise types are: {}.'
raise ValueError('Supported noise types are: {}.'.format(modes)) .format(self.modes))
self.mode = mode self.mode = mode
# mean and variance of the gaussian distribution the noise signal is # mean and variance of the gaussian distribution the noise signal is
...@@ -138,7 +304,19 @@ class Noise(InvariantTransform): ...@@ -138,7 +304,19 @@ class Noise(InvariantTransform):
self.exclude = exclude self.exclude = exclude
def __call__(self, image): def __call__(self, image):
"""Apply transformation.
Parameters
----------
image : `numpy.ndarray`
The image to transform.
Returns
-------
transform : `numpy.ndarray`
The transformed image
"""
if np.random.random(1) < self.p: if np.random.random(1) < self.p:
# transformation applied # transformation applied
...@@ -148,7 +326,8 @@ class Noise(InvariantTransform): ...@@ -148,7 +326,8 @@ class Noise(InvariantTransform):
noise = np.random.normal(self.mean, self.var, image.shape) noise = np.random.normal(self.mean, self.var, image.shape)
# check which values should not be modified by adding noise # check which values should not be modified by adding noise
noise[image == self.exclude] = 0 for val in self.exclude:
noise[image == val] = 0
if self.mode == 'gaussian': if self.mode == 'gaussian':
return (np.asarray(image) + noise).clip(0, 1) return (np.asarray(image) + noise).clip(0, 1)
...@@ -162,6 +341,14 @@ class Noise(InvariantTransform): ...@@ -162,6 +341,14 @@ class Noise(InvariantTransform):
return np.asarray(image) return np.asarray(image)
def __repr__(self): def __repr__(self):
"""Representation of the transformation.
Returns
-------
repr : `str`
Representation string.
"""
return self.__class__.__name__ + ('(mode = {}, mean = {}, var = {}, ' return self.__class__.__name__ + ('(mode = {}, mean = {}, var = {}, '
'p = {})' 'p = {})'
.format(self.mode, self.mean, .format(self.mode, self.mean,
...@@ -169,13 +356,47 @@ class Noise(InvariantTransform): ...@@ -169,13 +356,47 @@ class Noise(InvariantTransform):
class Augment(object): class Augment(object):
"""Apply a sequence of transformations.
Container class applying each transformation in ``transforms`` in order.
Parameters
----------
transforms : `list` or `tuple`
A sequence of instances of `pysegcnn.core.transforms.VariantTransform`
or `pysegcnn.core.transforms.InvariantTransform`.
Returns
-------
None.
"""
def __init__(self, transforms): def __init__(self, transforms):
assert isinstance(transforms, (list, tuple)) assert isinstance(transforms, (list, tuple))
self.transforms = transforms self.transforms = transforms
def __call__(self, image, gt): def __call__(self, image, gt):
"""Apply a sequence of transformations to ``image``.
For each spatially variant transformation, the ground truth ``gt`` is
transformed respectively.
Parameters
----------
image : `numpy.ndarray`
The input image.
gt : `numpy.ndarray`
The corresponding ground truth of ``image``.
Returns
-------
image : `numpy.ndarray`
The transformed image.
gt : `numpy.ndarray`
The transformed ground truth.
"""
# apply transformations to the input image in specified order # apply transformations to the input image in specified order
for t in self.transforms: for t in self.transforms:
image = t(image) image = t(image)
...@@ -199,6 +420,14 @@ class Augment(object): ...@@ -199,6 +420,14 @@ class Augment(object):
return image, gt return image, gt
def __repr__(self): def __repr__(self):
"""Representation of the container.
Returns
-------
repr : `str`
Representation string.
"""
fstring = self.__class__.__name__ + '(' fstring = self.__class__.__name__ + '('
for t in self.transforms: for t in self.transforms:
fstring += '\n' fstring += '\n'
......
"""Utility functions mainly for image IO and reshaping."""
# !/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Created on Tue Jul 14 15:02:23 2020
@author: Daniel
"""
# builtins # builtins
import os import os
import re import re
...@@ -15,16 +14,83 @@ import gdal ...@@ -15,16 +14,83 @@ import gdal
import torch import torch
import numpy as np import numpy as np
# the following functions are utility functions for common image
# manipulation operations
# module level logger # module level logger
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
# this function reads an image to a numpy array def img2np(path, tile_size=None, tile=None, pad=False, cval=0):
def img2np(path, tile_size=None, tile=None, pad=False, cval=0, verbose=False): """Read an image to a `numpy.ndarray`.
If ``tile_size`` is not `None`, the input image is divided into square
tiles of size (``tile_size``, ``tile_size``). If the image is not evenly
divisible and ``pad`` = False, a `ValueError` is raised. However, if
``pad`` = True, center padding with constant value ``cval`` is applied.
The tiling works as follows:
(Padded) Input image:
------------------------------------------------
| | | | |
| tile_00 | tile_01 | ... | tile_0n |
| | | | |
|----------------------------------------------|
| | | | |
| tile_10 | tile_11 | ... | tile_1n |
| | | | |
|----------------------------------------------|
| | | | |
| ... | ... | ... | ... |
| | | | |
|----------------------------------------------|
| | | | |
| tile_m0 | tile_m1 | ... | tile_mn |
| | | | |
------------------------------------------------
where m = n. Each tile has its id, which starts at 0 in the topleft corner
of the input image, i.e. tile_00 has id=0, and increases along the width
axis, i.e. tile_0n has id=n, tile_10 has id=n+1, ..., tile_mn has
id=(m * n) - 1.
If ``tile`` is an integer, only the tile with id = ``tile`` is returned.
Parameters
----------
path : `str` or `None` or `numpy.ndarray`
The image to read.
tile_size : `None` or `int`, optional
The size of a tile. The default is None.
tile : `int`, optional
The tile id. The default is None.
pad : `bool`, optional
Whether to center pad the input image. The default is False.
cval : `float`, optional
The constant padding value. The default is 0.
Raises
------
FileNotFoundError
Raised if ``path`` is a path that does not exist.
TypeError
Raised if ``path`` is not `str` or `None` or `numpy.ndarray`.
Returns
-------
image : `numpy.ndarray`
The image array. The output shape is:
if ``tile_size`` is not `None`:
shape=(tiles, bands, tile_size, tile_size)
if the image does only have one band:
shape=(tiles, tile_size, tile_size)
else:
shape=(bands, height, width)
if the image does only have one band:
shape=(height, width)
"""
# check the type of path # check the type of path
if isinstance(path, str): if isinstance(path, str):
if not os.path.exists(path): if not os.path.exists(path):
...@@ -51,7 +117,7 @@ def img2np(path, tile_size=None, tile=None, pad=False, cval=0, verbose=False): ...@@ -51,7 +117,7 @@ def img2np(path, tile_size=None, tile=None, pad=False, cval=0, verbose=False):
width = img.shape[2] width = img.shape[2]
else: else:
raise ValueError('Input of type {} not supported'.format(type(img))) raise TypeError('Input of type {} not supported'.format(type(img)))
# check whether to read the image in tiles # check whether to read the image in tiles
if tile_size is None: if tile_size is None:
...@@ -157,11 +223,31 @@ def img2np(path, tile_size=None, tile=None, pad=False, cval=0, verbose=False): ...@@ -157,11 +223,31 @@ def img2np(path, tile_size=None, tile=None, pad=False, cval=0, verbose=False):
return image return image
# this function checks whether an image is evenly divisible
# in square tiles of defined size tile_size
# if pad=True, a padding is returned to increase the image to the nearest size
# evenly fitting ntiles of size (tile_size, tile_size)
def is_divisible(img_size, tile_size, pad=False): def is_divisible(img_size, tile_size, pad=False):
"""Check whether an image is evenly divisible into square tiles.
Parameters
----------
img_size : `tuple`
The image size (height, width).
tile_size : `int`
The size of the tile.
pad : `bool`, optional
Whether to center pad the input image. The default is False.
Raises
------
ValueError
Raised if the image is not evenly divisible and ``pad`` = False.
Returns
-------
ntiles : `int`
The number of tiles fitting ``img_size``.
padding : `tuple`
The amount of padding (bottom, left, top, right).
"""
# calculate number of pixels per tile # calculate number of pixels per tile
pixels_per_tile = tile_size ** 2 pixels_per_tile = tile_size ** 2
...@@ -171,7 +257,7 @@ def is_divisible(img_size, tile_size, pad=False): ...@@ -171,7 +257,7 @@ def is_divisible(img_size, tile_size, pad=False):
# if it is evenly divisible, no padding is required # if it is evenly divisible, no padding is required
if ntiles.is_integer(): if ntiles.is_integer():
pad = 4 * (0,) padding = 4 * (0,)
if not ntiles.is_integer() and not pad: if not ntiles.is_integer() and not pad:
raise ValueError('Image of size {} not evenly divisible in ({}, {}) ' raise ValueError('Image of size {} not evenly divisible in ({}, {}) '
...@@ -193,30 +279,46 @@ def is_divisible(img_size, tile_size, pad=False): ...@@ -193,30 +279,46 @@ def is_divisible(img_size, tile_size, pad=False):
# in case both offsets are even, the padding is symmetric on both the # in case both offsets are even, the padding is symmetric on both the
# bottom/top and left/right # bottom/top and left/right
if not dh % 2 and not dw % 2: if not dh % 2 and not dw % 2:
pad = (dh // 2, dw // 2, dh // 2, dw // 2) padding = (dh // 2, dw // 2, dh // 2, dw // 2)
# in case only one offset is even, the padding is symmetric along the # in case only one offset is even, the padding is symmetric along the
# even offset and asymmetric along the odd offset # even offset and asymmetric along the odd offset
if not dh % 2 and dw % 2: if not dh % 2 and dw % 2:
pad = (dh // 2, dw // 2, dh // 2, dw // 2 + 1) padding = (dh // 2, dw // 2, dh // 2, dw // 2 + 1)
if dh % 2 and not dw % 2: if dh % 2 and not dw % 2:
pad = (dh // 2, dw // 2, dh // 2 + 1, dw // 2) padding = (dh // 2, dw // 2, dh // 2 + 1, dw // 2)
# in case of offsets are odd, the padding is asymmetric on both the # in case of offsets are odd, the padding is asymmetric on both the
# bottom/top and left/right # bottom/top and left/right
if dh % 2 and dw % 2: if dh % 2 and dw % 2:
pad = (dh // 2, dw // 2, dh // 2 + 1, dw // 2 + 1) padding = (dh // 2, dw // 2, dh // 2 + 1, dw // 2 + 1)
# calculate number of tiles on padded image # calculate number of tiles on padded image
ntiles = (h_new * w_new) / (tile_size ** 2) ntiles = (h_new * w_new) / (tile_size ** 2)
return int(ntiles), pad return int(ntiles), padding
# check whether a tile of size (tile_size, tile_size) with topleft corner at
# topleft exists in an image of size img_size
def check_tile_extend(img_size, topleft, tile_size): def check_tile_extend(img_size, topleft, tile_size):
"""Check if a tile exceeds the image size.
Parameters
----------
img_size : `tuple`
The image size (height, width).
topleft : `tuple`
The topleft corner of the tile (y, x).
tile_size : `int`
The size of the tile.
Returns
-------
nrows : `int`
Number of rows of the tile within the image.
ncols : TYPE
Number of columns of the tile within the image.
"""
# check if the tile is within both the rows and the columns of the image # check if the tile is within both the rows and the columns of the image
if (topleft[0] + tile_size < img_size[0] and if (topleft[0] + tile_size < img_size[0] and
topleft[1] + tile_size < img_size[1]): topleft[1] + tile_size < img_size[1]):
...@@ -246,11 +348,24 @@ def check_tile_extend(img_size, topleft, tile_size): ...@@ -246,11 +348,24 @@ def check_tile_extend(img_size, topleft, tile_size):
return nrows, ncols return nrows, ncols
# this function returns the top-left corners for each tile
# if the image is evenly divisible in square tiles of
# defined size tile_size
def tile_topleft_corner(img_size, tile_size): def tile_topleft_corner(img_size, tile_size):
"""Return the topleft corners of the tiles in the image.
Parameters
----------
img_size : `tuple`
The image size (height, width).
tile_size : `int`
The size of the tile.
Returns
-------
indices : `dict`
The keys of ``indices`` are the tile ids (`int`) and the values are the
topleft corners (`tuple` = (y, x)) of the tiles.
"""
# check if the image is divisible into square tiles of size # check if the image is divisible into square tiles of size
# (tile_size, tile_size) # (tile_size, tile_size)
_, _ = is_divisible(img_size, tile_size, pad=False) _, _ = is_divisible(img_size, tile_size, pad=False)
...@@ -273,7 +388,25 @@ def tile_topleft_corner(img_size, tile_size): ...@@ -273,7 +388,25 @@ def tile_topleft_corner(img_size, tile_size):
def reconstruct_scene(tiles, img_size, tile_size=None, nbands=1): def reconstruct_scene(tiles, img_size, tile_size=None, nbands=1):
"""Reconstruct a tiled image.
Parameters
----------
tiles : array_like
The tiled image, shape=(tiles, bands, tile_size, tile_size).
img_size : `tuple`
The size of the reconstructed image (height, width).
tile_size : `int` or `None`, optional
The size of the tile. The default is None.
nbands : `int`, optional
The number of bands of the reconstructed image. The default is 1.
Returns
-------
image : `numpy.ndarray`
The reconstructed image.
"""
# convert to numpy array # convert to numpy array
tiles = np.asarray(tiles) tiles = np.asarray(tiles)
...@@ -297,8 +430,22 @@ def reconstruct_scene(tiles, img_size, tile_size=None, nbands=1): ...@@ -297,8 +430,22 @@ def reconstruct_scene(tiles, img_size, tile_size=None, nbands=1):
return scene.squeeze() return scene.squeeze()
# function calculating prediction accuracy
def accuracy_function(outputs, labels): def accuracy_function(outputs, labels):
"""Calculate prediction accuracy.
Parameters
----------
outputs : `torch.Tensor` or array_like
The model prediction.
labels : `torch.Tensor` or array_like
The ground truth.
Returns
-------
accuracy : `float`
Mean prediction accuracy.
"""
if isinstance(outputs, torch.Tensor): if isinstance(outputs, torch.Tensor):
return (outputs == labels).float().mean().item() return (outputs == labels).float().mean().item()
else: else:
...@@ -306,7 +453,20 @@ def accuracy_function(outputs, labels): ...@@ -306,7 +453,20 @@ def accuracy_function(outputs, labels):
def parse_landsat_scene(scene_id): def parse_landsat_scene(scene_id):
"""Parse a Landsat scene identifier.
Parameters
----------
scene_id : `str`
A Landsat scene identifier.
Returns
-------
scene : `dict` or `None`
A dictionary containing scene metadata. If `None`, ``scene_id`` is not
a valid Landsat scene identifier.
"""
# Landsat Collection 1 naming convention in regular expression # Landsat Collection 1 naming convention in regular expression
sensor = 'L[COTEM]0[1-8]_' sensor = 'L[COTEM]0[1-8]_'
level = 'L[0-9][A-Z][A-Z]_' level = 'L[0-9][A-Z][A-Z]_'
...@@ -383,7 +543,20 @@ def parse_landsat_scene(scene_id): ...@@ -383,7 +543,20 @@ def parse_landsat_scene(scene_id):
def parse_sentinel2_scene(scene_id): def parse_sentinel2_scene(scene_id):
"""Parse a Sentinel-2 scene identifier.
Parameters
----------
scene_id : `str`
A Sentinel-2 scene identifier.
Returns
-------
scene : `dict` or `None`
A dictionary containing scene metadata. If `None`, ``scene_id`` is not
a valid Sentinel-2 scene identifier.
"""
# Sentinel 2 Level-1C products naming convention after 6th December 2016 # Sentinel 2 Level-1C products naming convention after 6th December 2016
mission = 'S2[A-B]_' mission = 'S2[A-B]_'
level = 'MSIL1C_' level = 'MSIL1C_'
...@@ -470,29 +643,48 @@ def parse_sentinel2_scene(scene_id): ...@@ -470,29 +643,48 @@ def parse_sentinel2_scene(scene_id):
def doy2date(year, doy): def doy2date(year, doy):
"""Converts the (year, day of the year) date format to a datetime object. """Convert the (year, day of the year) date format to a datetime object.
Parameters Parameters
---------- ----------
year : int year : `int`
the year The year
doy : int doy : `int`
the day of the year The day of the year
Returns Returns
------- -------
date : datetime.datetime date : `datetime.datetime`
the converted date as datetime object The converted date.
""" """
# convert year/day of year to a datetime object # convert year/day of year to a datetime object
date = (datetime.datetime(int(year), 1, 1) + date = (datetime.datetime(int(year), 1, 1) +
datetime.timedelta(days=(int(doy) - 1))) datetime.timedelta(days=(int(doy) - 1)))
return date return date
def item_in_enum(name, enum): def item_in_enum(name, enum):
"""Check if an item exists in an enumeration.
Parameters
----------
name : `str`
Name of the item.
enum : `enum.Enum`
An instance of `enum.Enum`.
Raises
------
ValueError
Raised if ``name`` is not in ``enum``.
Returns
-------
value
The value of ``name`` in ``enum``.
"""
# check whether the input name exists in the enumeration # check whether the input name exists in the enumeration
if name not in enum.__members__: if name not in enum.__members__:
raise ValueError('{} is not in {} enumeration. Valid names are: \n {}' raise ValueError('{} is not in {} enumeration. Valid names are: \n {}'
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment