Skip to content

odak.tools

odak.tools

Provides necessary definitions for general tools used across the library.

batch_of_rays(entry, exit)

Definition to generate a batch of rays with given entry point(s) and exit point(s). Note that the mapping is one to one, meaning nth item in your entry points list will exit from nth item in your exit list and generate that particular ray. Note that you can have a combination like nx3 points for entry or exit and 1 point for entry or exit. But if you have multiple points both for entry and exit, the number of points have to be same both for entry and exit.

Parameters:

  • entry
         Either a single point with size of 3 or multiple points with the size of nx3.
    
  • exit
         Either a single point with size of 3 or multiple points with the size of nx3.
    

Returns:

  • rays ( ndarray ) –

    Generated batch of rays.

Source code in odak/tools/sample.py
def batch_of_rays(entry, exit):
    """
    Definition to generate a batch of rays with given entry point(s) and exit point(s). Note that the mapping is one to one, meaning nth item in your entry points list will exit from nth item in your exit list and generate that particular ray. Note that you can have a combination like nx3 points for entry or exit and 1 point for entry or exit. But if you have multiple points both for entry and exit, the number of points have to be same both for entry and exit.

    Parameters
    ----------
    entry      : ndarray
                 Either a single point with size of 3 or multiple points with the size of nx3.
    exit       : ndarray
                 Either a single point with size of 3 or multiple points with the size of nx3.

    Returns
    ----------
    rays       : ndarray
                 Generated batch of rays.
    """
    norays = np.array([0, 0])
    if len(entry.shape) == 1:
        entry = entry.reshape((1, 3))
    if len(exit.shape) == 1:
        exit = exit.reshape((1, 3))
    norays = np.amax(np.asarray([entry.shape[0], exit.shape[0]]))
    if norays > exit.shape[0]:
        exit = np.repeat(exit, norays, axis=0)
    elif norays > entry.shape[0]:
        entry = np.repeat(entry, norays, axis=0)
    rays = []
    norays = int(norays)
    for i in range(norays):
        rays.append(create_ray_from_two_points(entry[i], exit[i]))
    rays = np.asarray(rays)
    return rays

blur_gaussian(field, kernel_length=[21, 21], nsigma=[3, 3])

A definition to blur a field using a Gaussian kernel.

Parameters:

  • field
            MxN field.
    
  • kernel_length (list, default: [21, 21] ) –
            Length of the Gaussian kernel along X and Y axes.
    
  • nsigma
            Sigma of the Gaussian kernel along X and Y axes.
    

Returns:

  • blurred_field ( ndarray ) –

    Blurred field.

Source code in odak/tools/matrix.py
def blur_gaussian(field, kernel_length=[21, 21], nsigma=[3, 3]):
    """
    A definition to blur a field using a Gaussian kernel.

    Parameters
    ----------
    field         : ndarray
                    MxN field.
    kernel_length : list
                    Length of the Gaussian kernel along X and Y axes.
    nsigma        : list
                    Sigma of the Gaussian kernel along X and Y axes.

    Returns
    ----------
    blurred_field : ndarray
                    Blurred field.
    """
    kernel = generate_2d_gaussian(kernel_length, nsigma)
    kernel = zero_pad(kernel, field.shape)
    blurred_field = convolve2d(field, kernel)
    blurred_field = blurred_field / np.amax(blurred_field)
    return blurred_field

box_volume_sample(no=[10, 10, 10], size=[100.0, 100.0, 100.0], center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate samples in a box volume.

Parameters:

  • no
          Number of samples.
    
  • size
          Physical size of the volume.
    
  • center
          Center location of the volume.
    
  • angles
          Tilt of the volume.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def box_volume_sample(
    no=[10, 10, 10],
    size=[100.0, 100.0, 100.0],
    center=[0.0, 0.0, 0.0],
    angles=[0.0, 0.0, 0.0],
):
    """
    Definition to generate samples in a box volume.

    Parameters
    ----------
    no          : list
                  Number of samples.
    size        : list
                  Physical size of the volume.
    center      : list
                  Center location of the volume.
    angles      : list
                  Tilt of the volume.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0], no[1], no[2], 3))
    x, y, z = np.mgrid[0 : no[0], 0 : no[1], 0 : no[2]]
    step = [size[0] / no[0], size[1] / no[1], size[2] / no[2]]
    samples[:, :, :, 0] = x * step[0] + step[0] / 2.0 - size[0] / 2.0
    samples[:, :, :, 1] = y * step[1] + step[1] / 2.0 - size[1] / 2.0
    samples[:, :, :, 2] = z * step[2] + step[2] / 2.0 - size[2] / 2.0
    samples = samples.reshape(
        (samples.shape[0] * samples.shape[1] * samples.shape[2], samples.shape[3])
    )
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

check_directory(directory, validate=True)

Definition to check if a directory exist. If it doesn't exist, this definition will create one.

Parameters:

  • directory
            Full directory path.
    
  • validate
            Whether to validate the path for security (default: True).
            When True, checks for path traversal, null bytes, and other unsafe patterns.
    

Returns:

  • bool ( bool ) –

    Returns True if directory already exists, False if created.

Raises:

  • ValueError : If path traversal attempt detected, invalid characters found,

    or directory creation fails due to permissions/invalid path.

  • TypeError : If directory is not a string.
Source code in odak/tools/file.py
def check_directory(directory, validate=True):
    """
    Definition to check if a directory exist. If it doesn't exist, this definition will create one.

    Parameters
    ----------
    directory     : str
                    Full directory path.
    validate      : bool
                    Whether to validate the path for security (default: True).
                    When True, checks for path traversal, null bytes, and other unsafe patterns.

    Returns
    -------
    bool         :  bool
                   Returns True if directory already exists, False if created.

    Raises
    ------
    ValueError   : If path traversal attempt detected, invalid characters found,
                   or directory creation fails due to permissions/invalid path.
    TypeError    : If directory is not a string.
    """
    if validate:
        logger.debug("Checking directory: {}".format(directory))
        safe_path = validate_path(directory + "/")
    else:
        # Bypass validation for internal use only
        safe_path = os.path.abspath(os.path.expanduser(directory))

    if not os.path.exists(safe_path):
        try:
            os.makedirs(safe_path)
            logger.info("Created directory: {}".format(safe_path))
            return False
        except Exception as e:
            raise ValueError(f"Failed to create directory '{safe_path}': {str(e)}")

    # Verify it's actually a directory, not a file with the same name
    if not os.path.isdir(safe_path):
        raise ValueError(f"Path exists but is not a directory: {safe_path}")

    logger.info("Directory already exists: {}".format(safe_path))
    return True

circular_sample(no=[10, 10], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate samples inside a circle over a surface.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of the circle.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def circular_sample(
    no=[10, 10], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate samples inside a circle over a surface.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of the circle.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0] + 1, no[1] + 1, 3))
    r_angles, r = np.mgrid[0 : no[0] + 1, 0 : no[1] + 1]
    r = r / np.amax(r) * radius
    r_angles = r_angles / np.amax(r_angles) * np.pi * 2
    samples[:, :, 0] = r * np.cos(r_angles)
    samples[:, :, 1] = r * np.sin(r_angles)
    samples = samples[1 : no[0] + 1, 1 : no[1] + 1, :]
    samples = samples.reshape((samples.shape[0] * samples.shape[1], samples.shape[2]))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

circular_uniform_random_sample(no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate sample inside a circle uniformly but randomly.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of the circle.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def circular_uniform_random_sample(
    no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate sample inside a circle uniformly but randomly.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of the circle.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.empty((0, 3))
    rs = np.sqrt(np.random.uniform(0, 1, no[0]))
    angs = np.random.uniform(0, 2 * np.pi, no[1])
    for i in rs:
        for angle in angs:
            r = radius * i
            point = np.array([float(r * np.cos(angle)), float(r * np.sin(angle)), 0])
            samples = np.vstack((samples, point))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

circular_uniform_sample(no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate sample inside a circle uniformly.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of the circle.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def circular_uniform_sample(
    no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate sample inside a circle uniformly.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of the circle.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.empty((0, 3))
    for i in range(0, no[0]):
        r = i / no[0] * radius
        ang_no = no[1] * i / no[0]
        for j in range(0, int(no[1] * i / no[0])):
            angle = j / ang_no * 2 * np.pi
            point = np.array([float(r * np.cos(angle)), float(r * np.sin(angle)), 0])
            samples = np.vstack((samples, point))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

closest_point_to_a_ray(point, ray)

Definition to calculate the point on a ray that is closest to given point.

Parameters:

  • point
            Given point in X,Y,Z.
    
  • ray
            Given ray.
    

Returns:

  • closest_point ( ndarray ) –

    Calculated closest point.

Source code in odak/tools/vector.py
def closest_point_to_a_ray(point, ray):
    """
    Definition to calculate the point on a ray that is closest to given point.

    Parameters
    ----------
    point         : list
                    Given point in X,Y,Z.
    ray           : ndarray
                    Given ray.

    Returns
    ---------
    closest_point : ndarray
                    Calculated closest point.
    """
    from odak.raytracing import propagate_a_ray

    if len(ray.shape) == 2:
        ray = ray.reshape((1, 2, 3))
    p0 = ray[:, 0]
    p1 = propagate_a_ray(ray, 1.0)
    if len(p1.shape) == 2:
        p1 = p1.reshape((1, 2, 3))
    p1 = p1[:, 0]
    p1 = p1.reshape(3)
    p0 = p0.reshape(3)
    point = point.reshape(3)
    closest_distance = -np.dot((p0 - point), (p1 - p0)) / np.sum((p1 - p0) ** 2)
    closest_point = propagate_a_ray(ray, closest_distance)[0]
    return closest_point

convert_bytes(num)

A definition to convert bytes to semantic scheme (MB,GB or alike). Inspired from https://stackoverflow.com/questions/2104080/how-can-i-check-file-size-in-python#2104083.

Parameters:

  • num
         Size in bytes
    

Returns:

  • num ( float ) –

    Size in new unit.

  • x ( str ) –

    New unit bytes, KB, MB, GB or TB.

Source code in odak/tools/file.py
def convert_bytes(num):
    """
    A definition to convert bytes to semantic scheme (MB,GB or alike). Inspired from https://stackoverflow.com/questions/2104080/how-can-i-check-file-size-in-python#2104083.


    Parameters
    ----------
    num        : float
                 Size in bytes


    Returns
    ----------
    num        : float
                 Size in new unit.
    x          : str
                 New unit bytes, KB, MB, GB or TB.
    """
    for x in ["bytes", "KB", "MB", "GB", "TB"]:
        if num < 1024.0:
            return num, x
        num /= 1024.0
    return None, None

convert_to_numpy(a)

A definition to convert Torch to Numpy.

Parameters:

  • a
         Input Torch array.
    

Returns:

  • b ( ndarray ) –

    Converted array.

Source code in odak/tools/conversions.py
def convert_to_numpy(a):
    """
    A definition to convert Torch to Numpy.

    Parameters
    ----------
    a          : torch.Tensor
                 Input Torch array.

    Returns
    ----------
    b          : numpy.ndarray
                 Converted array.
    """
    b = a.to("cpu").detach().numpy()
    return b

convert_to_torch(a, grad=True)

A definition to convert Numpy arrays to Torch.

Parameters:

  • a
         Input Numpy array.
    
  • grad
         Set if the converted array requires gradient.
    

Returns:

  • c ( Tensor ) –

    Converted array.

Source code in odak/tools/conversions.py
def convert_to_torch(a, grad=True):
    """
    A definition to convert Numpy arrays to Torch.

    Parameters
    ----------
    a          : ndarray
                 Input Numpy array.
    grad       : bool
                 Set if the converted array requires gradient.

    Returns
    ----------
    c          : torch.Tensor
                 Converted array.
    """
    b = np.copy(a)
    c = torch.from_numpy(b)
    c.requires_grad_(grad)
    return c

convolve2d(field, kernel)

Definition to convolve a field with a kernel by multiplying in frequency space.

Parameters:

  • field
          Input field with MxN shape.
    
  • kernel
          Input kernel with MxN shape.
    

Returns:

  • new_field ( ndarray ) –

    Convolved field.

Source code in odak/tools/matrix.py
def convolve2d(field, kernel):
    """
    Definition to convolve a field with a kernel by multiplying in frequency space.

    Parameters
    ----------
    field       : ndarray
                  Input field with MxN shape.
    kernel      : ndarray
                  Input kernel with MxN shape.

    Returns
    ----------
    new_field   : ndarray
                  Convolved field.
    """
    fr = np.fft.fft2(field)
    fr2 = np.fft.fft2(np.flipud(np.fliplr(kernel)))
    m, n = fr.shape
    new_field = np.real(np.fft.ifft2(fr * fr2))
    new_field = np.roll(new_field, int(-m / 2 + 1), axis=0)
    new_field = np.roll(new_field, int(-n / 2 + 1), axis=1)
    return new_field

copy_dict_with_keys(d, keys=None)

Copy elements from a dictionary based on specified keys.

This function creates and returns a new dictionary that includes only the key-value pairs from the input dictionary d whose keys are listed in keys. If no list of keys is provided (i.e., keys is None), all key-value pairs from d will be included.

Parameters:

  • d (dict) –
       The original dictionary to copy elements from.
    
  • keys
       A list of keys to include in the new dictionary. If not provided,
       all keys are copied. Defaults to None.
    

Returns:

  • new_dict ( dict ) –

    A new dictionary containing only the specified key-value pairs.

Examples:

>>> original_dict = {'a': 1, 'b': 2, 'c': 3}
>>> selected_keys = ['a', 'c']
>>> result = copy_dict_with_keys(original_dict, keys=selected_keys)
>>> print(result)  # Output: {'a': 1, 'c': 3}
Source code in odak/tools/variables.py
def copy_dict_with_keys(d: dict, keys=None):
    """
    Copy elements from a dictionary based on specified keys.

    This function creates and returns a new dictionary that includes only the
    key-value pairs from the input dictionary `d` whose keys are listed in
    `keys`. If no list of keys is provided (i.e., `keys` is None), all key-value
    pairs from `d` will be included.


    Parameters
    ----------
    d        : dict
               The original dictionary to copy elements from.
    keys     : list-like or None, optional
               A list of keys to include in the new dictionary. If not provided,
               all keys are copied. Defaults to None.


    Returns
    -------
    new_dict : dict
               A new dictionary containing only the specified key-value pairs.

    Examples
    --------
    >>> original_dict = {'a': 1, 'b': 2, 'c': 3}
    >>> selected_keys = ['a', 'c']
    >>> result = copy_dict_with_keys(original_dict, keys=selected_keys)
    >>> print(result)  # Output: {'a': 1, 'c': 3}
    """

    if not isinstance(d, dict):
        return None

    new_dict = {}
    if keys is None:
        new_dict.update(d)
    else:
        for key in keys:
            if key in d:
                new_dict[key] = d[key]

    return new_dict

copy_file(source, destination, follow_symlinks=True)

Definition to copy a file from one location to another.

Parameters:

  • source
              Source filename.
    
  • destination
              Destination filename.
    
  • follow_symlinks (bool, default: True ) –
              Set to True to follow the source of symbolic links.
    

Returns:

  • str ( The absolute path of the destination file on success. ) –

Raises:

  • ValueError : If source doesn't exist, destination is a directory,

    or copy fails due to permissions/disk space.

  • TypeError : If source or destination are not strings.
Source code in odak/tools/file.py
def copy_file(source, destination, follow_symlinks=True):
    """
    Definition to copy a file from one location to another.

    Parameters
    ----------
    source          : str
                      Source filename.
    destination     : str
                      Destination filename.
    follow_symlinks : bool
                      Set to True to follow the source of symbolic links.

    Returns
    -------
    str            : The absolute path of the destination file on success.

    Raises
    ------
    ValueError     : If source doesn't exist, destination is a directory,
                     or copy fails due to permissions/disk space.
    TypeError      : If source or destination are not strings.
    """
    # Validate both paths for security (path traversal, null bytes, etc.)
    safe_source = validate_path(source)
    safe_destination = validate_path(destination)

    # Verify source file exists
    if not os.path.exists(safe_source):
        raise ValueError(f"Source file does not exist: {safe_source}")

    # Verify source is a file, not a directory
    if not os.path.isfile(safe_source):
        raise ValueError(f"Source is not a file: {safe_source}")

    # Check if destination is an existing directory
    if os.path.isdir(safe_destination):
        raise ValueError(f"Destination is a directory, not a file: {safe_destination}")

    try:
        # Use copy2 to preserve file metadata (timestamps, permissions)
        return shutil.copy2(safe_source, safe_destination)
    except OSError as e:
        raise ValueError(
            f"Failed to copy '{safe_source}' to '{safe_destination}': {str(e)}"
        )

create_empty_list(dimensions=[1, 1])

A definition to create an empty Pythonic list.

Parameters:

  • dimensions
           Dimensions of the list to be created.
    

Returns:

  • new_list ( list ) –

    New empty list.

Source code in odak/tools/matrix.py
def create_empty_list(dimensions=[1, 1]):
    """
    A definition to create an empty Pythonic list.

    Parameters
    ----------
    dimensions   : list
                   Dimensions of the list to be created.

    Returns
    -------
    new_list     : list
                   New empty list.
    """
    new_list = 0
    for n in reversed(dimensions):
        new_list = [new_list] * n
    return new_list

create_group_tensor(number_of_elements, group_percentages=None, number_of_groups=None)

Create a tensor assigning elements to groups based on percentages.

This function distributes the given number of elements into groups according to the specified percentages. Each element is assigned a group ID based on the percentage distribution provided.

Parameters:

  • number_of_elements (int) –

    Total number of elements to distribute among groups.

  • group_percentages (list of float, default: None ) –

    List of percentages (0.0 to 1.0) specifying the proportion of elements in each group. Must sum to 1.0. If None, equal distribution is used based on number_of_groups. Default: None.

  • number_of_groups (int, default: None ) –

    Number of groups to create. Required when group_percentages is None. Default: None.

Returns:

  • Tensor

    A 1D tensor of shape (number_of_elements,) where each element contains its assigned group ID (0-indexed). Dtype is torch.long.

Raises:

  • ValueError

    If group_percentages does not sum to 1.0. If any percentage is outside the range [0.0, 1.0]. If number_of_groups is None when group_percentages is None.

  • TypeError

    If number_of_elements is not an integer.

Examples:

>>> # Create a tensor with 10 elements distributed into 3 groups
>>> percentages = [0.5, 0.3, 0.2]
>>> result = create_group_tensor(10, percentages)
>>> print(result)
tensor([0, 0, 0, 0, 0, 1, 1, 1, 2, 2])
>>> # Create a tensor with 100 elements equally distributed
>>> percentages = [0.34, 0.33, 0.33]
>>> result = create_group_tensor(100, percentages)
>>> print(len(result))
100
>>> # Create a tensor with equal distribution using number_of_groups
>>> result = create_group_tensor(10, number_of_groups=4)
>>> print(result)
tensor([0, 0, 1, 1, 2, 2, 2, 3, 3, 3])
Source code in odak/tools/variables.py
def create_group_tensor(
    number_of_elements: int,
    group_percentages: Optional[list] = None,
    number_of_groups: Optional[int] = None,
) -> torch.Tensor:
    """
    Create a tensor assigning elements to groups based on percentages.

    This function distributes the given number of elements into groups according
    to the specified percentages. Each element is assigned a group ID based on
    the percentage distribution provided.

    Parameters
    ----------
    number_of_elements : int
        Total number of elements to distribute among groups.
    group_percentages : list of float, optional
        List of percentages (0.0 to 1.0) specifying the proportion of elements
        in each group. Must sum to 1.0. If None, equal distribution is used
        based on number_of_groups. Default: None.
    number_of_groups : int, optional
        Number of groups to create. Required when group_percentages is None.
        Default: None.

    Returns
    -------
    torch.Tensor
        A 1D tensor of shape (number_of_elements,) where each element contains
        its assigned group ID (0-indexed). Dtype is torch.long.

    Raises
    ------
    ValueError
        If group_percentages does not sum to 1.0.
        If any percentage is outside the range [0.0, 1.0].
        If number_of_groups is None when group_percentages is None.
    TypeError
        If number_of_elements is not an integer.

    Examples
    --------
    >>> # Create a tensor with 10 elements distributed into 3 groups
    >>> percentages = [0.5, 0.3, 0.2]
    >>> result = create_group_tensor(10, percentages)
    >>> print(result)
    tensor([0, 0, 0, 0, 0, 1, 1, 1, 2, 2])

    >>> # Create a tensor with 100 elements equally distributed
    >>> percentages = [0.34, 0.33, 0.33]
    >>> result = create_group_tensor(100, percentages)
    >>> print(len(result))
    100

    >>> # Create a tensor with equal distribution using number_of_groups
    >>> result = create_group_tensor(10, number_of_groups=4)
    >>> print(result)
    tensor([0, 0, 1, 1, 2, 2, 2, 3, 3, 3])
    """
    # Validate number_of_elements type
    if not isinstance(number_of_elements, int):
        logger.error(
            f"number_of_elements must be an integer, got {type(number_of_elements).__name__}",
        )
        raise TypeError(
            f"number_of_elements must be an integer, got {type(number_of_elements).__name__}"
        )

    # Handle None group_percentages by creating equal distribution
    if group_percentages is None:
        if number_of_groups is None:
            logger.error(
                "number_of_groups must be provided when group_percentages is None"
            )
            raise ValueError(
                "number_of_groups must be provided when group_percentages is None"
            )
        group_percentages = [1.0 / number_of_groups] * number_of_groups

    # Validate input percentages sum to 1.0
    total_percentage = sum(group_percentages)
    if abs(total_percentage - 1.0) > 1e-6:
        logger.error(
            f"group_percentages must sum to 1.0, got {total_percentage}"
        )
        raise ValueError(
            f"group_percentages must sum to 1.0, got {total_percentage}"
        )

    # Validate all percentages are within valid range
    if any(p < 0 or p > 1 for p in group_percentages):
        logger.error(
            "All percentages must be between 0.0 and 1.0"
        )
        raise ValueError(
            "All percentages must be between 0.0 and 1.0"
        )

    # Calculate number of elements per group
    group_sizes = []
    remaining_elements = number_of_elements
    cumulative_percentage = 0.0  # Reserved for potential future use

    for i, pct in enumerate(group_percentages):
        if i == len(group_percentages) - 1:
            # Last group gets remaining elements to ensure exact total
            group_sizes.append(remaining_elements)
        else:
            size = int(round(number_of_elements * pct))
            group_sizes.append(size)
            remaining_elements -= size

    # Create tensor with group assignments
    groups = []
    for group_id, size in enumerate(group_sizes):
        if size > 0:
            groups.extend([group_id] * size)

    return torch.tensor(groups, dtype=torch.long)

create_ray_from_two_points(x0y0z0, x1y1z1)

Definition to create a ray from two given points. Note that both inputs must match in shape.

Parameters:

  • x0y0z0
           List that contains X,Y and Z start locations of a ray (3). It can also be a list of points as well (mx3). This is the starting point.
    
  • x1y1z1
           List that contains X,Y and Z ending locations of a ray (3). It can also be a list of points as well (mx3). This is the end point.
    

Returns:

  • ray ( ndarray ) –

    Array that contains starting points and cosines of a created ray.

Source code in odak/raytracing/ray.py
def create_ray_from_two_points(x0y0z0, x1y1z1):
    """
    Definition to create a ray from two given points. Note that both inputs must match in shape.

    Parameters
    ----------
    x0y0z0       : list
                   List that contains X,Y and Z start locations of a ray (3). It can also be a list of points as well (mx3). This is the starting point.
    x1y1z1       : list
                   List that contains X,Y and Z ending locations of a ray (3). It can also be a list of points as well (mx3). This is the end point.

    Returns
    ----------
    ray          : ndarray
                   Array that contains starting points and cosines of a created ray.
    """
    x0y0z0 = np.asarray(x0y0z0, dtype=np.float64)
    x1y1z1 = np.asarray(x1y1z1, dtype=np.float64)
    if len(x0y0z0.shape) == 1:
        x0y0z0 = x0y0z0.reshape((1, 3))
    if len(x1y1z1.shape) == 1:
        x1y1z1 = x1y1z1.reshape((1, 3))
    xdiff = x1y1z1[:, 0] - x0y0z0[:, 0]
    ydiff = x1y1z1[:, 1] - x0y0z0[:, 1]
    zdiff = x1y1z1[:, 2] - x0y0z0[:, 2]
    s = np.sqrt(xdiff**2 + ydiff**2 + zdiff**2)
    s[s == 0] = np.nan
    cosines = np.zeros((xdiff.shape[0], 3))
    cosines[:, 0] = xdiff / s
    cosines[:, 1] = ydiff / s
    cosines[:, 2] = zdiff / s
    ray = np.zeros((xdiff.shape[0], 2, 3), dtype=np.float64)
    ray[:, 0] = x0y0z0
    ray[:, 1] = cosines
    if ray.shape[0] == 1:
        ray = ray.reshape((2, 3))
    return ray

crop_center(field, size=None)

Definition to crop the center of a field with 2Mx2N size. The outcome is a MxN array.

Parameters:

  • field
          Input field 2Mx2N array.
    

Returns:

  • cropped ( ndarray ) –

    Cropped version of the input field.

Source code in odak/tools/matrix.py
def crop_center(field, size=None):
    """
    Definition to crop the center of a field with 2Mx2N size. The outcome is a MxN array.

    Parameters
    ----------
    field       : ndarray
                  Input field 2Mx2N array.

    Returns
    ----------
    cropped     : ndarray
                  Cropped version of the input field.
    """
    if type(size) == type(None):
        qx = int(np.ceil(field.shape[0]) / 4)
        qy = int(np.ceil(field.shape[1]) / 4)
        cropped = np.copy(field[qx : 3 * qx, qy : 3 * qy])
    else:
        cx = int(np.ceil(field.shape[0] / 2))
        cy = int(np.ceil(field.shape[1] / 2))
        hx = int(np.ceil(size[0] / 2))
        hy = int(np.ceil(size[1] / 2))
        cropped = np.copy(field[cx - hx : cx + hx, cy - hy : cy + hy])
    return cropped

cross_product(vector1, vector2)

Definition to cross product two vectors and return the resultant vector. Used method described under: http://en.wikipedia.org/wiki/Cross_product

Parameters:

  • vector1
           A vector/ray.
    
  • vector2
           A vector/ray.
    

Returns:

  • ray ( ndarray ) –

    Array that contains starting points and cosines of a created ray.

Source code in odak/tools/vector.py
def cross_product(vector1, vector2):
    """
    Definition to cross product two vectors and return the resultant vector. Used method described under: http://en.wikipedia.org/wiki/Cross_product

    Parameters
    ----------
    vector1      : ndarray
                   A vector/ray.
    vector2      : ndarray
                   A vector/ray.

    Returns
    ----------
    ray          : ndarray
                   Array that contains starting points and cosines of a created ray.
    """
    angle = np.cross(vector1[1].T, vector2[1].T)
    angle = np.asarray(angle)
    ray = np.array([vector1[0], angle], dtype=np.float32)
    return ray

distance_between_point_clouds(points0, points1)

A definition to find distance between every point in one cloud to other points in the other point cloud.

Parameters:

  • points0
          Mx3 points.
    
  • points1
          Nx3 points.
    

Returns:

  • distances ( ndarray ) –

    MxN distances.

Source code in odak/tools/vector.py
def distance_between_point_clouds(points0, points1):
    """
    A definition to find distance between every point in one cloud to other points in the other point cloud.
    Parameters
    ----------
    points0     : ndarray
                  Mx3 points.
    points1     : ndarray
                  Nx3 points.

    Returns
    ----------
    distances   : ndarray
                  MxN distances.
    """
    c = points1.reshape((1, points1.shape[0], points1.shape[1]))
    a = np.repeat(c, points0.shape[0], axis=0)
    b = points0.reshape((points0.shape[0], 1, points0.shape[1]))
    b = np.repeat(b, a.shape[1], axis=1)
    distances = np.sqrt(np.sum((a - b) ** 2, axis=2))
    return distances

distance_between_two_points(point1, point2)

Definition to calculate distance between two given points.

Parameters:

  • point1
          First point in X,Y,Z.
    
  • point2
          Second point in X,Y,Z.
    

Returns:

  • distance ( float ) –

    Distance in between given two points.

Source code in odak/tools/vector.py
def distance_between_two_points(point1, point2):
    """
    Definition to calculate distance between two given points.

    Parameters
    ----------
    point1      : list
                  First point in X,Y,Z.
    point2      : list
                  Second point in X,Y,Z.

    Returns
    ----------
    distance    : float
                  Distance in between given two points.
    """
    point1 = np.asarray(point1)
    point2 = np.asarray(point2)
    if len(point1.shape) == 1 and len(point2.shape) == 1:
        distance = np.sqrt(np.sum((point1 - point2) ** 2))
    elif len(point1.shape) == 2 or len(point2.shape) == 2:
        distance = np.sqrt(np.sum((point1 - point2) ** 2, axis=1))
    return distance

expanduser(filename)

Definition to decode filename using namespaces and shortcuts.

Parameters:

  • filename
            Filename.
    

Returns:

  • new_filename ( str ) –

    Filename.

Source code in odak/tools/file.py
def expanduser(filename):
    """
    Definition to decode filename using namespaces and shortcuts.


    Parameters
    ----------
    filename      : str
                    Filename.


    Returns
    -------
    new_filename  : str
                    Filename.
    """
    new_filename = os.path.expanduser(filename)
    return new_filename

generate_2d_gaussian(kernel_length=[21, 21], nsigma=[3, 3])

Generate 2D Gaussian kernel. Inspired from https://stackoverflow.com/questions/29731726/how-to-calculate-a-gaussian-kernel-matrix-efficiently-in-numpy

Parameters:

  • kernel_length (list, default: [21, 21] ) –
            Length of the Gaussian kernel along X and Y axes.
    
  • nsigma
            Sigma of the Gaussian kernel along X and Y axes.
    

Returns:

  • kernel_2d ( ndarray ) –

    Generated Gaussian kernel.

Source code in odak/tools/matrix.py
def generate_2d_gaussian(kernel_length=[21, 21], nsigma=[3, 3]):
    """
    Generate 2D Gaussian kernel. Inspired from https://stackoverflow.com/questions/29731726/how-to-calculate-a-gaussian-kernel-matrix-efficiently-in-numpy

    Parameters
    ----------
    kernel_length : list
                    Length of the Gaussian kernel along X and Y axes.
    nsigma        : list
                    Sigma of the Gaussian kernel along X and Y axes.

    Returns
    ----------
    kernel_2d     : ndarray
                    Generated Gaussian kernel.
    """
    x = np.linspace(-nsigma[0], nsigma[0], kernel_length[0] + 1)
    y = np.linspace(-nsigma[1], nsigma[1], kernel_length[1] + 1)
    xx, yy = np.meshgrid(x, y)
    kernel_2d = np.exp(
        -0.5
        * (np.square(xx) / np.square(nsigma[0]) + np.square(yy) / np.square(nsigma[1]))
    )
    kernel_2d = kernel_2d / kernel_2d.sum()
    return kernel_2d

generate_bandlimits(size=[512, 512], levels=9)

A definition to calculate octaves used in bandlimiting frequencies in the frequency domain.

Parameters:

  • size
         Size of each mask in octaves.
    

Returns:

  • masks ( ndarray ) –

    Masks (Octaves).

Source code in odak/tools/matrix.py
def generate_bandlimits(size=[512, 512], levels=9):
    """
    A definition to calculate octaves used in bandlimiting frequencies in the frequency domain.

    Parameters
    ----------
    size       : list
                 Size of each mask in octaves.

    Returns
    ----------
    masks      : ndarray
                 Masks (Octaves).
    """
    masks = np.zeros((levels, size[0], size[1]))
    cx = int(size[0] / 2)
    cy = int(size[1] / 2)
    for i in range(0, masks.shape[0]):
        deltax = int((size[0]) / (2 ** (i + 1)))
        deltay = int((size[1]) / (2 ** (i + 1)))
        masks[i, cx - deltax : cx + deltax, cy - deltay : cy + deltay] = 1.0
        masks[
            i,
            int(cx - deltax / 2.0) : int(cx + deltax / 2.0),
            int(cy - deltay / 2.0) : int(cy + deltay / 2.0),
        ] = 0.0
    masks = np.asarray(masks)
    return masks

get_base_filename(filename)

Definition to retrieve the base filename and extension type.

Parameters:

  • filename
             Input filename.
    

Returns:

  • basename ( str ) –

    Basename extracted from the filename.

  • extension ( str ) –

    Extension extracted from the filename.

Source code in odak/tools/file.py
def get_base_filename(filename):
    """
    Definition to retrieve the base filename and extension type.


    Parameters
    ----------
    filename       : str
                     Input filename.


    Returns
    -------
    basename       : str
                     Basename extracted from the filename.
    extension      : str
                     Extension extracted from the filename.
    """
    cache = os.path.basename(filename)
    basename = os.path.splitext(cache)[0]
    extension = os.path.splitext(cache)[1]
    return basename, extension

grid_sample(no=[10, 10], size=[100.0, 100.0], center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate samples over a surface.

Parameters:

  • no
          Number of samples.
    
  • size
          Physical size of the surface.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def grid_sample(
    no=[10, 10], size=[100.0, 100.0], center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate samples over a surface.

    Parameters
    ----------
    no          : list
                  Number of samples.
    size        : list
                  Physical size of the surface.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0], no[1], 3))
    step = [size[0] / (no[0] - 1), size[1] / (no[1] - 1)]
    x, y = np.mgrid[0 : no[0], 0 : no[1]]
    samples[:, :, 0] = x * step[0] - size[0] / 2.0
    samples[:, :, 1] = y * step[1] - size[1] / 2.0
    samples = samples.reshape((samples.shape[0] * samples.shape[1], samples.shape[2]))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

list_directories(path, recursive=True)

Lists directories inside a given directory, recursively if specified.

Parameters:

  • path
        The path to the directory you want to list.
    
  • recursive (bool, default: True ) –
        If True, lists subdirectories recursively. Defaults to True.
    

Returns:

  • list

    A list of directory names.

Source code in odak/tools/file.py
def list_directories(path, recursive=True):
    """
    Lists directories inside a given directory, recursively if specified.

    Parameters
    ----------
    path      : str
                The path to the directory you want to list.
    recursive : bool, optional
                If True, lists subdirectories recursively. Defaults to True.

    Returns
    -------
    list
                A list of directory names.
    """
    directories = []
    safe_path = validate_path(path + "/")
    if recursive:
        for entry in os.listdir(safe_path):
            full_path = os.path.join(safe_path, entry)
            if os.path.isdir(full_path):
                directories.append(entry)
                directories.extend(list_directories(full_path, recursive=True))
    else:
        contents = os.listdir(safe_path)
        directories = [f for f in contents if os.path.isdir(os.path.join(safe_path, f))]
    return sorted(directories)

list_files(path, key='*.*', recursive=True)

Definition to list files in a given path with a given key.

Parameters:

  • path
          Path to a folder.
    
  • key
          Key used for scanning a path.
    
  • recursive
          If set True, scan the path recursively.
    

Returns:

  • files_list ( ndarray ) –

    list of files found in a given path.

Source code in odak/tools/file.py
def list_files(path, key="*.*", recursive=True):
    """
    Definition to list files in a given path with a given key.


    Parameters
    ----------
    path        : str
                  Path to a folder.
    key         : str
                  Key used for scanning a path.
    recursive   : bool
                  If set True, scan the path recursively.


    Returns
    -------
    files_list  : ndarray
                  list of files found in a given path.
    """
    safe_path = validate_path(path + "/")
    search_result = None
    if recursive == True:
        search_result = pathlib.Path(safe_path).rglob(key)
    elif recursive == False:
        search_result = pathlib.Path(safe_path).glob(key)
    if search_result is None:
        return []
    files_list = [str(item) for item in search_result]
    return sorted(files_list)

load_dictionary(filename)

Definition to load a dictionary (JSON) file.

Parameters:

  • filename
            Filename.
    

Returns:

  • settings ( dict ) –

    Dictionary read from the file.

Source code in odak/tools/file.py
def load_dictionary(filename):
    """
    Definition to load a dictionary (JSON) file.


    Parameters
    ----------
    filename      : str
                    Filename.


    Returns
    ----------
    settings      : dict
                    Dictionary read from the file.

    """
    logger.info("Loading dictionary: {}".format(filename))
    safe_path = validate_path(filename, allowed_extensions=[".json"])
    with open(safe_path, "r", encoding="utf-8") as f:
        settings = json.load(f)
    logger.info("Loaded dictionary: {}".format(safe_path))
    return settings

load_image(fn, normalizeby=0.0, torch_style=False)

Definition to load an image from a given location as a Numpy array.

Parameters:

  • fn
            Filename.
    
  • normalizeby
            Value to to normalize images with. Default value of zero will lead to no normalization.
    
  • torch_style
            If set True, it will load an image mxnx3 as 3xmxn.
    

Returns:

  • image ( ndarray ) –

    Image loaded as a Numpy array.

Source code in odak/tools/file.py
def load_image(fn, normalizeby=0.0, torch_style=False):
    """
    Definition to load an image from a given location as a Numpy array.


    Parameters
    ----------
    fn           : str
                    Filename.
    normalizeby  : float
                    Value to to normalize images with. Default value of zero will lead to no normalization.
    torch_style  : bool
                    If set True, it will load an image mxnx3 as 3xmxn.


    Returns
    ----------
    image        :  ndarray
                    Image loaded as a Numpy array.

    """
    logger.info("Loading image: {}".format(fn))
    safe_path = validate_path(
        fn,
        allowed_extensions=[
            ".png",
            ".jpg",
            ".jpeg",
            ".bmp",
            ".tiff",
            ".tif",
            ".gif",
            ".webp",
            ".pbm",
            ".pgm",
            ".ppm",
            ".sr",
            ".ras",
        ],
    )
    image = cv2.imread(safe_path, cv2.IMREAD_UNCHANGED)
    if isinstance(image, type(None)):
        raise ValueError(
            f"Failed to load image from '{safe_path}'. "
            f"Check file format, permissions, and that the file exists."
        )
    if len(image.shape) > 2:
        new_image = np.copy(image)
        new_image[:, :, 0] = image[:, :, 2]
        new_image[:, :, 2] = image[:, :, 0]
        image = new_image
    if normalizeby != 0.0:
        image = image * 1.0 / normalizeby
    if torch_style == True and len(image.shape) > 2:
        image = np.moveaxis(image, -1, 0)
    logger.info("Loaded image: {}".format(safe_path))
    return image.astype(float)

nufft2(field, fx, fy, size=None, sign=1, eps=10 ** -12)

A definition to take 2D Non-Uniform Fast Fourier Transform (NUFFT).

Parameters:

  • field
          Input field.
    
  • fx
          Frequencies along x axis.
    
  • fy
          Frequencies along y axis.
    
  • size
          Size.
    
  • sign
          Sign of the exponential used in NUFFT kernel.
    
  • eps
          Accuracy of NUFFT.
    

Returns:

  • result ( ndarray ) –

    Inverse NUFFT of the input field.

Source code in odak/tools/matrix.py
def nufft2(field, fx, fy, size=None, sign=1, eps=10 ** (-12)):
    """
    A definition to take 2D Non-Uniform Fast Fourier Transform (NUFFT).

    Parameters
    ----------
    field       : ndarray
                  Input field.
    fx          : ndarray
                  Frequencies along x axis.
    fy          : ndarray
                  Frequencies along y axis.
    size        : list
                  Size.
    sign        : float
                  Sign of the exponential used in NUFFT kernel.
    eps         : float
                  Accuracy of NUFFT.

    Returns
    ----------
    result      : ndarray
                  Inverse NUFFT of the input field.
    """
    try:
        import finufft
    except:
        print("odak.tools.nufft2 requires finufft to be installed: pip install finufft")
    image = np.copy(field).astype(np.complex128)
    result = finufft.nufft2d2(fx.flatten(), fy.flatten(), image, eps=eps, isign=sign)
    if type(size) == type(None):
        result = result.reshape(field.shape)
    else:
        result = result.reshape(size)
    return result

nuifft2(field, fx, fy, size=None, sign=1, eps=10 ** -12)

A definition to take 2D Adjoint Non-Uniform Fast Fourier Transform (NUFFT).

Parameters:

  • field
          Input field.
    
  • fx
          Frequencies along x axis.
    
  • fy
          Frequencies along y axis.
    
  • size
          Shape of the NUFFT calculated for an input field.
    
  • sign
          Sign of the exponential used in NUFFT kernel.
    
  • eps
          Accuracy of NUFFT.
    

Returns:

  • result ( ndarray ) –

    NUFFT of the input field.

Source code in odak/tools/matrix.py
def nuifft2(field, fx, fy, size=None, sign=1, eps=10 ** (-12)):
    """
    A definition to take 2D Adjoint Non-Uniform Fast Fourier Transform (NUFFT).

    Parameters
    ----------
    field       : ndarray
                  Input field.
    fx          : ndarray
                  Frequencies along x axis.
    fy          : ndarray
                  Frequencies along y axis.
    size        : list or ndarray
                  Shape of the NUFFT calculated for an input field.
    sign        : float
                  Sign of the exponential used in NUFFT kernel.
    eps         : float
                  Accuracy of NUFFT.

    Returns
    ----------
    result      : ndarray
                  NUFFT of the input field.
    """
    try:
        import finufft
    except:
        print(
            "odak.tools.nuifft2 requires finufft to be installed: pip install finufft"
        )
    image = np.copy(field).astype(np.complex128)
    if type(size) == type(None):
        result = finufft.nufft2d1(
            fx.flatten(),
            fy.flatten(),
            image.flatten(),
            image.shape,
            eps=eps,
            isign=sign,
        )
    else:
        result = finufft.nufft2d1(
            fx.flatten(),
            fy.flatten(),
            image.flatten(),
            (size[0], size[1]),
            eps=eps,
            isign=sign,
        )
    result = np.asarray(result)
    return result

point_to_ray_distance(point, ray_point_0, ray_point_1)

Definition to find point's closest distance to a line represented with two points.

Parameters:

  • point
          Point to be tested.
    
  • ray_point_0 (ndarray) –
          First point to represent a line.
    
  • ray_point_1 (ndarray) –
          Second point to represent a line.
    

Returns:

  • distance ( float ) –

    Calculated distance.

Source code in odak/tools/vector.py
def point_to_ray_distance(point, ray_point_0, ray_point_1):
    """
    Definition to find point's closest distance to a line represented with two points.

    Parameters
    ----------
    point       : ndarray
                  Point to be tested.
    ray_point_0 : ndarray
                  First point to represent a line.
    ray_point_1 : ndarray
                  Second point to represent a line.

    Returns
    ----------
    distance    : float
                  Calculated distance.
    """
    distance = np.sum(
        np.cross((point - ray_point_0), (point - ray_point_1)) ** 2
    ) / np.sum((ray_point_1 - ray_point_0) ** 2)
    return distance

quantize(image_field, bits=4)

Definitio to quantize a image field (0-255, 8 bit) to a certain bits level.

Parameters:

  • image_field (ndarray) –
          Input image field.
    
  • bits
          A value in between 0 to 8. Can not be zero.
    

Returns:

  • new_field ( ndarray ) –

    Quantized image field.

Source code in odak/tools/matrix.py
def quantize(image_field, bits=4):
    """
    Definitio to quantize a image field (0-255, 8 bit) to a certain bits level.

    Parameters
    ----------
    image_field : ndarray
                  Input image field.
    bits        : int
                  A value in between 0 to 8. Can not be zero.

    Returns
    ----------
    new_field   : ndarray
                  Quantized image field.
    """
    divider = 2 ** (8 - bits)
    new_field = image_field / divider
    new_field = new_field.astype(np.int64)
    return new_field

random_sample_point_cloud(point_cloud, no, p=None)

Definition to pull a subset of points from a point cloud with a given probability.

Parameters:

  • point_cloud
           Point cloud array.
    
  • no
           Number of samples.
    
  • p
           Probability list in the same size as no.
    

Returns:

  • subset ( ndarray ) –

    Subset of the given point cloud.

Source code in odak/tools/sample.py
def random_sample_point_cloud(point_cloud, no, p=None):
    """
    Definition to pull a subset of points from a point cloud with a given probability.

    Parameters
    ----------
    point_cloud  : ndarray
                   Point cloud array.
    no           : list
                   Number of samples.
    p            : list
                   Probability list in the same size as no.

    Returns
    ----------
    subset       : ndarray
                   Subset of the given point cloud.
    """
    choice = np.random.choice(point_cloud.shape[0], no, p)
    subset = point_cloud[choice, :]
    return subset

read_PLY(fn, offset=[0, 0, 0], angles=[0.0, 0.0, 0.0], mode='XYZ')

Definition to read a PLY file and extract meshes from a given PLY file. Note that rotation is always with respect to 0,0,0.

Parameters:

  • fn
           Filename of a PLY file.
    
  • offset
           Offset in X,Y,Z.
    
  • angles
           Rotation angles in degrees.
    
  • mode
           Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    

Returns:

  • triangles ( ndarray ) –

    Triangles from a given PLY file. Note that the triangles coming out of this function isn't always structured in the right order and with the size of (MxN)x3. You can use numpy's reshape to restructure it to mxnx3 if you know what you are doing.

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If fn is not a string.
Source code in odak/tools/asset.py
def read_PLY(fn, offset=[0, 0, 0], angles=[0.0, 0.0, 0.0], mode="XYZ"):
    """
    Definition to read a PLY file and extract meshes from a given PLY file. Note that rotation is always with respect to 0,0,0.

    Parameters
    ----------
    fn           : string
                   Filename of a PLY file.
    offset       : ndarray
                   Offset in X,Y,Z.
    angles       : list
                   Rotation angles in degrees.
    mode         : str
                   Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.

    Returns
    ----------
    triangles    : ndarray
                   Triangles from a given PLY file. Note that the triangles coming out of this function isn't always structured in the right order and with the size of (MxN)x3. You can use numpy's reshape to restructure it to mxnx3 if you know what you are doing.

    Raises
    ------
    ValueError   : If path validation fails or extension is not allowed.
    TypeError    : If fn is not a string.
    """
    if np.__name__ != "numpy":
        import numpy as np_ply
    else:
        np_ply = np
    safe_path = validate_path(fn, allowed_extensions=[".ply"])
    with open(safe_path, "rb") as f:
        plydata = PlyData.read(f)
    triangle_ids = np_ply.vstack(plydata["face"].data["vertex_indices"])
    triangles = []
    for vertex_ids in triangle_ids:
        triangle = [
            rotate_point(
                plydata["vertex"][int(vertex_ids[0])].tolist(),
                angles=angles,
                offset=offset,
            )[0],
            rotate_point(
                plydata["vertex"][int(vertex_ids[1])].tolist(),
                angles=angles,
                offset=offset,
            )[0],
            rotate_point(
                plydata["vertex"][int(vertex_ids[2])].tolist(),
                angles=angles,
                offset=offset,
            )[0],
        ]
        triangle = np_ply.asarray(triangle)
        triangles.append(triangle)
    triangles = np_ply.array(triangles)
    triangles = np.asarray(triangles, dtype=np.float32)
    return triangles

read_PLY_point_cloud(filename)

Definition to read a PLY file as a point cloud.

Parameters:

  • filename
           Filename of a PLY file.
    

Returns:

  • point_cloud ( ndarray ) –

    An array filled with poitns from the PLY file.

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If filename is not a string.
Source code in odak/tools/asset.py
def read_PLY_point_cloud(filename):
    """
    Definition to read a PLY file as a point cloud.

    Parameters
    ----------
    filename     : str
                   Filename of a PLY file.

    Returns
    ----------
    point_cloud  : ndarray
                   An array filled with poitns from the PLY file.

    Raises
    ------
    ValueError   : If path validation fails or extension is not allowed.
    TypeError    : If filename is not a string.
    """
    safe_path = validate_path(filename, allowed_extensions=[".ply"])
    plydata = PlyData.read(safe_path)
    if np.__name__ != "numpy":
        import numpy as np_ply

        point_cloud = np_ply.zeros((plydata["vertex"][:].shape[0], 3))
        point_cloud[:, 0] = np_ply.asarray(plydata["vertex"]["x"][:])
        point_cloud[:, 1] = np_ply.asarray(plydata["vertex"]["y"][:])
        point_cloud[:, 2] = np_ply.asarray(plydata["vertex"]["z"][:])
        point_cloud = np.asarray(point_cloud)
    else:
        point_cloud = np.zeros((plydata["vertex"][:].shape[0], 3))
        point_cloud[:, 0] = np.asarray(plydata["vertex"]["x"][:])
        point_cloud[:, 1] = np.asarray(plydata["vertex"]["y"][:])
        point_cloud[:, 2] = np.asarray(plydata["vertex"]["z"][:])
    return point_cloud

read_text_file(filename)

Definition to read a given text file and convert it into a Pythonic list.

Parameters:

  • filename
              Source filename (i.e. test.txt).
    

Returns:

  • content ( list ) –

    Pythonic string list containing the text from the file provided.

Raises:

  • ValueError : If path validation fails or unsafe characters detected.
Source code in odak/tools/file.py
def read_text_file(filename):
    """
    Definition to read a given text file and convert it into a Pythonic list.


    Parameters
    ----------
    filename        : str
                      Source filename (i.e. test.txt).


    Returns
    -------
    content         : list
                      Pythonic string list containing the text from the file provided.

    Raises
    ------
    ValueError     : If path validation fails or unsafe characters detected.
    """
    content = []
    safe_path = validate_path(filename)
    with open(safe_path, "r", encoding="utf-8") as f:
        for line in f:
            content.append(line.rstrip())
    return content

resize_image(img, target_size)

Definition to resize a given image to a target shape.

Parameters:

  • img
            MxN image to be resized.
            Image must be normalized (0-1).
    
  • target_size
            Target shape.
    

Returns:

  • img ( ndarray ) –

    Resized image.

Source code in odak/tools/file.py
def resize_image(img, target_size):
    """
    Definition to resize a given image to a target shape.


    Parameters
    ----------
    img           : ndarray
                    MxN image to be resized.
                    Image must be normalized (0-1).
    target_size   : list
                    Target shape.


    Returns
    ----------
    img           : ndarray
                    Resized image.

    """
    logger.debug("Resizing image to {}".format(target_size))
    img = cv2.resize(
        img, dsize=(target_size[0], target_size[1]), interpolation=cv2.INTER_AREA
    )
    logger.debug("Image resized to {}".format(target_size))
    return img

rotate_point(point, angles=[0, 0, 0], mode='XYZ', origin=[0, 0, 0], offset=[0, 0, 0])

Definition to rotate a given point. Note that rotation is always with respect to 0,0,0.

Parameters:

  • point
           A point.
    
  • angles
           Rotation angles in degrees.
    
  • mode
           Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    
  • origin
           Reference point for a rotation.
    
  • offset
           Shift with the given offset.
    

Returns:

  • result ( ndarray ) –

    Result of the rotation

  • rotx ( ndarray ) –

    Rotation matrix along X axis.

  • roty ( ndarray ) –

    Rotation matrix along Y axis.

  • rotz ( ndarray ) –

    Rotation matrix along Z axis.

Source code in odak/tools/transformation.py
def rotate_point(
    point, angles=[0, 0, 0], mode="XYZ", origin=[0, 0, 0], offset=[0, 0, 0]
):
    """
    Definition to rotate a given point. Note that rotation is always with respect to 0,0,0.

    Parameters
    ----------
    point        : ndarray
                   A point.
    angles       : list
                   Rotation angles in degrees.
    mode         : str
                   Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    origin       : list
                   Reference point for a rotation.
    offset       : list
                   Shift with the given offset.

    Returns
    ----------
    result       : ndarray
                   Result of the rotation
    rotx         : ndarray
                   Rotation matrix along X axis.
    roty         : ndarray
                   Rotation matrix along Y axis.
    rotz         : ndarray
                   Rotation matrix along Z axis.
    """
    point = np.asarray(point)
    point -= np.asarray(origin)
    rotx = rotmatx(angles[0])
    roty = rotmaty(angles[1])
    rotz = rotmatz(angles[2])
    if mode == "XYZ":
        result = np.dot(rotz, np.dot(roty, np.dot(rotx, point)))
    elif mode == "XZY":
        result = np.dot(roty, np.dot(rotz, np.dot(rotx, point)))
    elif mode == "YXZ":
        result = np.dot(rotz, np.dot(rotx, np.dot(roty, point)))
    elif mode == "ZXY":
        result = np.dot(roty, np.dot(rotx, np.dot(rotz, point)))
    elif mode == "ZYX":
        result = np.dot(rotx, np.dot(roty, np.dot(rotz, point)))
    result += np.asarray(origin)
    result += np.asarray(offset)
    return result, rotx, roty, rotz

rotate_points(points, angles=[0, 0, 0], mode='XYZ', origin=[0, 0, 0], offset=[0, 0, 0])

Definition to rotate points.

Parameters:

  • points
           Points.
    
  • angles
           Rotation angles in degrees.
    
  • mode
           Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    
  • origin
           Reference point for a rotation.
    
  • offset
           Shift with the given offset.
    

Returns:

  • result ( ndarray ) –

    Result of the rotation

Source code in odak/tools/transformation.py
def rotate_points(
    points, angles=[0, 0, 0], mode="XYZ", origin=[0, 0, 0], offset=[0, 0, 0]
):
    """
    Definition to rotate points.

    Parameters
    ----------
    points       : ndarray
                   Points.
    angles       : list
                   Rotation angles in degrees.
    mode         : str
                   Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    origin       : list
                   Reference point for a rotation.
    offset       : list
                   Shift with the given offset.

    Returns
    ----------
    result       : ndarray
                   Result of the rotation
    """
    points = np.asarray(points)
    if angles[0] == 0 and angles[1] == 0 and angles[2] == 0:
        result = np.array(offset) + points
        return result
    points -= np.array(origin)
    rotx = rotmatx(angles[0])
    roty = rotmaty(angles[1])
    rotz = rotmatz(angles[2])
    if mode == "XYZ":
        result = np.dot(rotz, np.dot(roty, np.dot(rotx, points.T))).T
    elif mode == "XZY":
        result = np.dot(roty, np.dot(rotz, np.dot(rotx, points.T))).T
    elif mode == "YXZ":
        result = np.dot(rotz, np.dot(rotx, np.dot(roty, points.T))).T
    elif mode == "ZXY":
        result = np.dot(roty, np.dot(rotx, np.dot(rotz, points.T))).T
    elif mode == "ZYX":
        result = np.dot(rotx, np.dot(roty, np.dot(rotz, points.T))).T
    result += np.array(origin)
    result += np.array(offset)
    return result

rotmatx(angle)

Definition to generate a rotation matrix along X axis.

Parameters:

  • angle
           Rotation angles in degrees.
    

Returns:

  • rotx ( ndarray ) –

    Rotation matrix along X axis.

Source code in odak/tools/transformation.py
def rotmatx(angle):
    """
    Definition to generate a rotation matrix along X axis.

    Parameters
    ----------
    angle        : list
                   Rotation angles in degrees.

    Returns
    -------
    rotx         : ndarray
                   Rotation matrix along X axis.
    """
    angle = np.float64(angle)
    angle = np.radians(angle)
    rotx = np.array(
        [
            [1.0, 0.0, 0.0],
            [0.0, math.cos(angle), -math.sin(angle)],
            [0.0, math.sin(angle), math.cos(angle)],
        ],
        dtype=np.float64,
    )
    return rotx

rotmaty(angle)

Definition to generate a rotation matrix along Y axis.

Parameters:

  • angle
           Rotation angles in degrees.
    

Returns:

  • roty ( ndarray ) –

    Rotation matrix along Y axis.

Source code in odak/tools/transformation.py
def rotmaty(angle):
    """
    Definition to generate a rotation matrix along Y axis.

    Parameters
    ----------
    angle        : list
                   Rotation angles in degrees.

    Returns
    -------
    roty         : ndarray
                   Rotation matrix along Y axis.
    """
    angle = np.radians(angle)
    roty = np.array(
        [
            [math.cos(angle), 0.0, math.sin(angle)],
            [0.0, 1.0, 0.0],
            [-math.sin(angle), 0.0, math.cos(angle)],
        ],
        dtype=np.float64,
    )
    return roty

rotmatz(angle)

Definition to generate a rotation matrix along Z axis.

Parameters:

  • angle
           Rotation angles in degrees.
    

Returns:

  • rotz ( ndarray ) –

    Rotation matrix along Z axis.

Source code in odak/tools/transformation.py
def rotmatz(angle):
    """
    Definition to generate a rotation matrix along Z axis.

    Parameters
    ----------
    angle        : list
                   Rotation angles in degrees.

    Returns
    -------
    rotz         : ndarray
                   Rotation matrix along Z axis.
    """
    angle = np.radians(angle)
    rotz = np.array(
        [
            [math.cos(angle), -math.sin(angle), 0.0],
            [math.sin(angle), math.cos(angle), 0.0],
            [0.0, 0.0, 1.0],
        ],
        dtype=np.float64,
    )

    return rotz

same_side(p1, p2, a, b)

Definition to figure which side a point is on with respect to a line and a point. See http://www.blackpawn.com/texts/pointinpoly/ for more. If p1 and p2 are on the sameside, this definition returns True.

Parameters:

  • p1
          Point(s) to check.
    
  • p2
          This is the point check against.
    
  • a
          First point that forms the line.
    
  • b
          Second point that forms the line.
    
Source code in odak/tools/vector.py
def same_side(p1, p2, a, b):
    """
    Definition to figure which side a point is on with respect to a line and a point. See http://www.blackpawn.com/texts/pointinpoly/ for more. If p1 and p2 are on the sameside, this definition returns True.

    Parameters
    ----------
    p1          : list
                  Point(s) to check.
    p2          : list
                  This is the point check against.
    a           : list
                  First point that forms the line.
    b           : list
                  Second point that forms the line.
    """
    ba = np.subtract(b, a)
    p1a = np.subtract(p1, a)
    p2a = np.subtract(p2, a)
    cp1 = np.cross(ba, p1a)
    cp2 = np.cross(ba, p2a)
    test = np.dot(cp1, cp2)
    if len(p1.shape) > 1:
        return test >= 0
    if test >= 0:
        return True
    return False

save_dictionary(settings, filename)

Definition to load a dictionary (JSON) file.

Parameters:

  • settings
            Dictionary read from the file.
    
  • filename
            Filename.
    
Source code in odak/tools/file.py
def save_dictionary(settings, filename):
    """
    Definition to load a dictionary (JSON) file.


    Parameters
    ----------
    settings      : dict
                    Dictionary read from the file.
    filename      : str
                    Filename.
    """
    logger.info("Saving dictionary: {}".format(filename))
    safe_path = validate_path(filename, allowed_extensions=[".json"])
    with open(safe_path, "w", encoding="utf-8") as f:
        json.dump(settings, f, ensure_ascii=False, indent=4)
    logger.info("Saved dictionary: {}".format(safe_path))
    return settings

save_image(fn, img, cmin=0, cmax=255, color_depth=8)

Definition to save a Numpy array as an image.

Parameters:

  • fn
            Filename.
    
  • img
            A numpy array with NxMx3 or NxMx1 shapes.
    
  • cmin
            Minimum value that will be interpreted as 0 level in the final image.
    
  • cmax
            Maximum value that will be interpreted as 255 level in the final image.
    
  • color_depth
            Pixel color depth in bits, default is eight bits.
    

Returns:

  • bool ( bool ) –

    True if successful.

Source code in odak/tools/file.py
def save_image(fn, img, cmin=0, cmax=255, color_depth=8):
    """
    Definition to save a Numpy array as an image.


    Parameters
    ----------
    fn           : str
                    Filename.
    img          : ndarray
                    A numpy array with NxMx3 or NxMx1 shapes.
    cmin         : int
                    Minimum value that will be interpreted as 0 level in the final image.
    cmax         : int
                    Maximum value that will be interpreted as 255 level in the final image.
    color_depth  : int
                    Pixel color depth in bits, default is eight bits.


    Returns
    ----------
    bool         :  bool
                    True if successful.

    """
    logger.info("Saving image: {}".format(fn))
    input_img = np.copy(img).astype(np.float32)
    cmin = float(cmin)
    cmax = float(cmax)
    input_img[input_img < cmin] = cmin
    input_img[input_img > cmax] = cmax
    input_img /= cmax
    input_img = input_img * 1.0 * (2**color_depth - 1)
    input_img = np.nan_to_num(input_img, nan=0.0, posinf=cmax, neginf=cmin)
    if color_depth == 8:
        input_img = input_img.astype(np.uint8)
    elif color_depth == 16:
        input_img = input_img.astype(np.uint16)
    if len(input_img.shape) > 2:
        if input_img.shape[2] > 1:
            cache_img = np.copy(input_img)
            cache_img[:, :, 0] = input_img[:, :, 2]
            cache_img[:, :, 2] = input_img[:, :, 0]
            input_img = cache_img
    safe_path = validate_path(
        fn, allowed_extensions=[".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"]
    )
    cv2.imwrite(safe_path, input_img)
    logger.info("Saved image: {}".format(safe_path))
    return True

shell_command(cmd, cwd='.', timeout=None, check=True)

Definition to initiate shell commands securely.

Parameters:

  • cmd
           Command to be executed as a list of arguments.
           Example: ['blender', '-b', 'file.blend']
    
  • cwd
           Working directory. Default is current directory.
    
  • timeout
           Timeout in seconds if the process isn't complete.
           If None, no timeout is enforced.
    
  • check
           Set it to True to return results and enable timeout. False returns only process.
    

Returns:

  • proc ( Popen ) –

    Generated process handle.

  • outs ( str or bytes ) –

    Standard output of the executed command (None when check=False).

  • errs ( str or bytes ) –

    Standard error of the executed command (None when check=False).

Raises:

  • ValueError : If command contains dangerous characters, forbidden commands,

    null bytes, or if working directory path is invalid.

  • TypeError : If cmd is not a list or cwd is not a string.
  • subprocess.TimeoutExpired : If process exceeds timeout (when check=True).
Security Features
  • Command whitelist validation (blender, dispynode.py, ffmpeg, python, etc.)
  • Blocks shell metacharacters (; & | ` $ < > quotes)
  • Null byte injection protection
  • Path traversal blocked in working directory
  • Uses Popen with shell=False for safe execution
Example

proc, outs, errs = shell_command(['blender', '-b', 'scene.blend']) proc, None, None = shell_command(['python', 'script.py'], check=False)

Source code in odak/tools/file.py
def shell_command(cmd, cwd=".", timeout=None, check=True):
    """
    Definition to initiate shell commands securely.

    Parameters
    ----------
    cmd          : list
                   Command to be executed as a list of arguments.
                   Example: ['blender', '-b', 'file.blend']
    cwd          : str
                   Working directory. Default is current directory.
    timeout      : int or None
                   Timeout in seconds if the process isn't complete.
                   If None, no timeout is enforced.
    check        : bool
                   Set it to True to return results and enable timeout. False returns only process.

    Returns
    -------
    proc         : subprocess.Popen
                   Generated process handle.
    outs         : str or bytes
                   Standard output of the executed command (None when check=False).
    errs         : str or bytes
                   Standard error of the executed command (None when check=False).

    Raises
    ------
    ValueError   : If command contains dangerous characters, forbidden commands,
                   null bytes, or if working directory path is invalid.
    TypeError    : If cmd is not a list or cwd is not a string.
    subprocess.TimeoutExpired : If process exceeds timeout (when check=True).

    Security Features
    ---------------
    - Command whitelist validation (blender, dispynode.py, ffmpeg, python, etc.)
    - Blocks shell metacharacters (; & | ` $ < > quotes)
    - Null byte injection protection
    - Path traversal blocked in working directory
    - Uses Popen with shell=False for safe execution

    Example
    ------
    >>> proc, outs, errs = shell_command(['blender', '-b', 'scene.blend'])
    >>> proc, None, None = shell_command(['python', 'script.py'], check=False)
    """
    # Validate command arguments
    validated_cmd = validate_shell_command(cmd)

    # Validate working directory
    safe_cwd = validate_cwd(cwd if isinstance(cwd, str) else ".")

    # Execute with shell=False for security (prevents shell injection)
    proc = subprocess.Popen(
        validated_cmd,
        cwd=safe_cwd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        shell=False,  # Critical: Prevents shell metacharacter injection
    )

    if not check:
        return proc, None, None

    try:
        outs, errs = proc.communicate(timeout=timeout)
    except subprocess.TimeoutExpired:
        proc.kill()
        outs, errs = proc.communicate()

    return proc, outs, errs

size_of_a_file(file_path)

A definition to get size of a file with a relevant unit.

Parameters:

  • file_path
         Path of the file.
    

Returns:

  • a ( float ) –

    Size of the file.

  • b ( str ) –

    Unit of the size (bytes, KB, MB, GB or TB).

Source code in odak/tools/file.py
def size_of_a_file(file_path):
    """
    A definition to get size of a file with a relevant unit.


    Parameters
    ----------
    file_path  : float
                 Path of the file.


    Returns
    ----------
    a          : float
                 Size of the file.
    b          : str
                 Unit of the size (bytes, KB, MB, GB or TB).
    """
    if os.path.isfile(file_path):
        file_info = os.stat(file_path)
        a, b = convert_bytes(file_info.st_size)
        return a, b
    return None, None

sphere_sample(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2])

Definition to generate a regular sample set on the surface of a sphere using polar coordinates.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of a sphere.
    
  • center
          Center of a sphere.
    
  • k
          Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def sphere_sample(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2]):
    """
    Definition to generate a regular sample set on the surface of a sphere using polar coordinates.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of a sphere.
    center      : list
                  Center of a sphere.
    k           : list
                  Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0], no[1], 3))
    psi, teta = np.mgrid[0 : no[0], 0 : no[1]]
    psi = k[0] * np.pi / no[0] * psi
    teta = k[1] * np.pi / no[1] * teta
    samples[:, :, 0] = center[0] + radius * np.sin(psi) * np.cos(teta)
    samples[:, :, 1] = center[0] + radius * np.sin(psi) * np.sin(teta)
    samples[:, :, 2] = center[0] + radius * np.cos(psi)
    samples = samples.reshape((no[0] * no[1], 3))
    return samples

sphere_sample_uniform(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2])

Definition to generate an uniform sample set on the surface of a sphere using polar coordinates.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of a sphere.
    
  • center
          Center of a sphere.
    
  • k
          Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def sphere_sample_uniform(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2]):
    """
    Definition to generate an uniform sample set on the surface of a sphere using polar coordinates.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of a sphere.
    center      : list
                  Center of a sphere.
    k           : list
                  Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.


    Returns
    ----------
    samples     : ndarray
                  Samples generated.

    """
    samples = np.zeros((no[0], no[1], 3))
    row = np.arange(0, no[0])
    psi, teta = np.mgrid[0 : no[0], 0 : no[1]]
    for psi_id in range(0, no[0]):
        psi[psi_id] = np.roll(row, psi_id, axis=0)
        teta[psi_id] = np.roll(row, -psi_id, axis=0)
    psi = k[0] * np.pi / no[0] * psi
    teta = k[1] * np.pi / no[1] * teta
    samples[:, :, 0] = center[0] + radius * np.sin(psi) * np.cos(teta)
    samples[:, :, 1] = center[1] + radius * np.sin(psi) * np.sin(teta)
    samples[:, :, 2] = center[2] + radius * np.cos(psi)
    samples = samples.reshape((no[0] * no[1], 3))
    return samples

tilt_towards(location, lookat)

Definition to tilt surface normal of a plane towards a point.

Parameters:

  • location
           Center of the plane to be tilted.
    
  • lookat
           Tilt towards this point.
    

Returns:

  • angles ( list ) –

    Rotation angles in degrees.

Source code in odak/tools/transformation.py
def tilt_towards(location, lookat):
    """
    Definition to tilt surface normal of a plane towards a point.

    Parameters
    ----------
    location     : list
                   Center of the plane to be tilted.
    lookat       : list
                   Tilt towards this point.

    Returns
    ----------
    angles       : list
                   Rotation angles in degrees.
    """
    dx = location[0] - lookat[0]
    dy = location[1] - lookat[1]
    dz = location[2] - lookat[2]
    dist = np.sqrt(dx**2 + dy**2 + dz**2)
    phi = np.arctan2(dy, dx)
    theta = np.arccos(dz / dist)
    angles = [0, np.degrees(theta).tolist(), np.degrees(phi).tolist()]
    return angles

validate_cwd(cwd)

Validates working directory path.

Parameters:

  • cwd
              Working directory path.
    

Returns:

  • safe_cwd ( str ) –

    The validated and absolute path.

Raises:

  • ValueError : If path contains dangerous characters.
Source code in odak/tools/file.py
def validate_cwd(cwd):
    """
    Validates working directory path.

    Parameters
    ----------
    cwd             : str
                      Working directory path.

    Returns
    -------
    safe_cwd        : str
                      The validated and absolute path.

    Raises
    ------
    ValueError      : If path contains dangerous characters.
    """
    if not isinstance(cwd, str):
        raise TypeError(f"Working directory must be a string, got {type(cwd).__name__}")

    if "\x00" in cwd:
        raise ValueError("Null bytes detected in working directory path")

    expanded = os.path.expanduser(cwd)
    absolute_path = os.path.abspath(expanded)

    # Check if the directory exists (optional, can be removed if you want to allow non-existent dirs)
    # if not os.path.isdir(absolute_path):
    #     raise ValueError(f"Working directory does not exist: {absolute_path}")

    return absolute_path

validate_path(path, allowed_extensions=None)

Validates a file path for security safety.

Parameters:

  • path
              Path to validate.
    
  • allowed_extensions (list, default: None ) –
                  List of allowed extensions (e.g., ['.png', '.jpg']).
                  If None, all extensions are allowed.
    

Returns:

  • safe_path ( str ) –

    The validated and secured path (with tilde expanded).

Raises:

  • ValueError : If path traversal attempt detected or extension not allowed.
  • TypeError : If path is not a string.
Source code in odak/tools/file.py
def validate_path(path, allowed_extensions=None):
    """
    Validates a file path for security safety.

    Parameters
    ----------
    path            : str
                      Path to validate.
    allowed_extensions : list, optional
                          List of allowed extensions (e.g., ['.png', '.jpg']).
                          If None, all extensions are allowed.

    Returns
    -------
    safe_path       : str
                      The validated and secured path (with tilde expanded).

    Raises
    ------
    ValueError      : If path traversal attempt detected or extension not allowed.
    TypeError       : If path is not a string.
    """
    if not isinstance(path, str):
        raise TypeError(f"Path must be a string, got {type(path).__name__}")

    # Check for null bytes before expanding user (Windows path injection)
    if "\x00" in path:
        raise ValueError("Null bytes not allowed in path")

    # Check for path traversal patterns BEFORE expanding
    if ".." in path.split(os.sep) or ".." in path.replace(os.sep, "/").split("/"):
        if re.search(r"(^|[/\\])\.\.([/\\]|$)", path):
            raise ValueError("Path traversal detected: '..' not allowed in path")

    # Check for URL protocols before expanding
    path_lower = path.lower()
    if re.search(r"https?://|ftp://", path_lower):
        raise ValueError("URL protocols not allowed in file paths")

    path = os.path.expanduser(path)
    resolved_path = os.path.abspath(path)

    # Check for UNC or device paths on Windows
    if re.match(r"\\\\\\\|\\\\\\?\.\\", path) or path.startswith("//."):
        raise ValueError("UNC/device paths not allowed")

    if len(resolved_path) > 260:  # Windows MAX_PATH limit
        raise ValueError("Path exceeds maximum allowed length (260 characters)")

    if allowed_extensions is not None:
        _, file_ext = os.path.splitext(path)
        ext_lower = file_ext.lower()
        allowed_normalized = [
            ext.lower() if ext.startswith(".") else f".{ext}"
            for ext in allowed_extensions
        ]
        if ext_lower not in allowed_normalized:
            raise ValueError(
                f"File extension '{file_ext}' is not allowed. "
                f"Allowed: {allowed_extensions}"
            )

    logger.debug(f"Path validated: {path}")
    return resolved_path

validate_positive_parameter(value, name='value', allow_zero=False)

Validate that a parameter is positive (or non-negative if allow_zero).

Parameters:

  • value
                Value to validate.
    
  • name
                Parameter name for error messages.
    
  • allow_zero
                If True, zero values are allowed. Default: False.
    

Returns:

  • validated_value ( same type as input ) –

    The validated value (unchanged if valid).

Raises:

  • ValueError : If value is negative or zero (when not allowed).
  • TypeError : If value is not a number.
Source code in odak/tools/variables.py
def validate_positive_parameter(value, name="value", allow_zero=False):
    """
    Validate that a parameter is positive (or non-negative if allow_zero).

    Parameters
    ----------
    value             : float or np.ndarray or torch.Tensor
                        Value to validate.
    name              : str
                        Parameter name for error messages.
    allow_zero        : bool
                        If True, zero values are allowed. Default: False.

    Returns
    -------
    validated_value   : same type as input
                        The validated value (unchanged if valid).

    Raises
    ------
    ValueError        : If value is negative or zero (when not allowed).
    TypeError         : If value is not a number.
    """
    if isinstance(value, (int, float)):
        if not allow_zero and value <= 0:
            raise ValueError(f"{name} must be positive, got {value}")
        elif allow_zero and value < 0:
            raise ValueError(f"{name} must be non-negative, got {value}")

    elif isinstance(value, np.ndarray):
        if not allow_zero and np.any(value <= 0):
            raise ValueError(f"{name} contains negative or zero values")
        elif allow_zero and np.any(value < 0):
            raise ValueError(f"{name} contains negative values")

    elif isinstance(value, torch.Tensor):
        if not allow_zero and torch.any(value <= 0):
            raise ValueError(f"{name} contains negative or zero values")
        elif allow_zero and torch.any(value < 0):
            raise ValueError(f"{name} contains negative values")

    else:
        raise TypeError(f"{name} must be numeric, got {type(value).__name__}")

    logger.debug(
        f"Validated {name}: {value if isinstance(value, (int, float)) else f'{value.shape}'}"
    )
    return value

validate_shell_command(cmd_list)

Validates shell command arguments for security.

Parameters:

  • cmd_list
              List of command arguments to validate.
    

Returns:

  • validated_list ( list ) –

    The validated and sanitized command list.

Raises:

  • ValueError : If command contains dangerous characters or is not allowed.
  • TypeError : If cmd_list is not a list.
Source code in odak/tools/file.py
def validate_shell_command(cmd_list):
    """
    Validates shell command arguments for security.

    Parameters
    ----------
    cmd_list        : list
                      List of command arguments to validate.

    Returns
    -------
    validated_list  : list
                      The validated and sanitized command list.

    Raises
    ------
    ValueError      : If command contains dangerous characters or is not allowed.
    TypeError       : If cmd_list is not a list.
    """
    if not isinstance(cmd_list, list):
        raise TypeError(f"Command must be a list, got {type(cmd_list).__name__}")

    if len(cmd_list) == 0:
        raise ValueError("Command list cannot be empty")

    validated = []
    for idx, arg in enumerate(cmd_list):
        if not isinstance(arg, str):
            raise TypeError(
                f"Command argument {idx} must be a string, got {type(arg).__name__}"
            )

        # Check for null bytes
        if "\x00" in arg:
            raise ValueError(f"Null bytes detected in command argument {idx}")

        # Check for dangerous shell metacharacters
        for pattern in DANGEROUS_PATTERNS:
            if re.search(pattern, arg):
                raise ValueError(
                    f"Dangerous character detected in command argument {idx}: '{arg[:50]}...'. "
                    f"Shell metacharacters are not allowed."
                )

        # Check if the base command (first argument) is in whitelist
        if idx == 0:
            base_cmd = os.path.basename(arg).lower()
            if base_cmd not in ALLOWED_COMMANDS and not arg.startswith("/"):
                # Allow absolute paths, but warn
                logger.warning(
                    f"Command '{base_cmd}' is not in the allowed whitelist. "
                    f"Allowed commands: {sorted(ALLOWED_COMMANDS)}"
                )

        validated.append(arg)

    logger.debug(f"Shell command validated successfully")
    return validated

write_PLY(triangles, savefn='output.ply')

Definition to generate a PLY file from given points.

Parameters:

  • triangles
          List of triangles with the size of Mx3x3.
    
  • savefn
          Filename for a PLY file.
    

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If savefn is not a string.
Source code in odak/tools/asset.py
def write_PLY(triangles, savefn="output.ply"):
    """
    Definition to generate a PLY file from given points.

    Parameters
    ----------
    triangles   : ndarray
                  List of triangles with the size of Mx3x3.
    savefn      : string
                  Filename for a PLY file.

    Raises
    ------
    ValueError  : If path validation fails or extension is not allowed.
    TypeError   : If savefn is not a string.
    """
    safe_path = validate_path(savefn, allowed_extensions=[".ply"])
    tris = []
    pnts = []
    color = [255, 255, 255]
    for tri_id in range(triangles.shape[0]):
        tris.append(
            ([3 * tri_id, 3 * tri_id + 1, 3 * tri_id + 2], color[0], color[1], color[2])
        )
        for i in range(0, 3):
            pnts.append(
                (
                    float(triangles[tri_id][i][0]),
                    float(triangles[tri_id][i][1]),
                    float(triangles[tri_id][i][2]),
                )
            )
    tris = np.asarray(
        tris,
        dtype=[
            ("vertex_indices", "i4", (3,)),
            ("red", "u1"),
            ("green", "u1"),
            ("blue", "u1"),
        ],
    )
    pnts = np.asarray(pnts, dtype=[("x", "f4"), ("y", "f4"), ("z", "f4")])
    # Save mesh.
    el1 = PlyElement.describe(pnts, "vertex", comments=["Vertex data"])
    el2 = PlyElement.describe(tris, "face", comments=["Face data"])
    PlyData([el1, el2], text="True").write(safe_path)

write_PLY_from_points(points, savefn='output.ply')

Definition to generate a PLY file from given points.

Parameters:

  • points
          List of points with the size of MxNx3.
    
  • savefn
          Filename for a PLY file.
    

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If savefn is not a string.
Source code in odak/tools/asset.py
def write_PLY_from_points(points, savefn="output.ply"):
    """
    Definition to generate a PLY file from given points.

    Parameters
    ----------
    points      : ndarray
                  List of points with the size of MxNx3.
    savefn      : string
                  Filename for a PLY file.

    Raises
    ------
    ValueError  : If path validation fails or extension is not allowed.
    TypeError   : If savefn is not a string.
    """
    safe_path = validate_path(savefn, allowed_extensions=[".ply"])
    if np.__name__ != "numpy":
        import numpy as np_ply
    else:
        np_ply = np
    # Generate equation
    samples = [points.shape[0], points.shape[1]]
    # Generate vertices.
    pnts = []
    tris = []
    for idx in range(0, samples[0]):
        for idy in range(0, samples[1]):
            pnt = (points[idx, idy, 0], points[idx, idy, 1], points[idx, idy, 2])
            pnts.append(pnt)
    color = [255, 255, 255]
    for idx in range(0, samples[0] - 1):
        for idy in range(0, samples[1] - 1):
            tris.append(
                (
                    [
                        idy + (idx + 1) * samples[0],
                        idy + idx * samples[0],
                        idy + 1 + idx * samples[0],
                    ],
                    color[0],
                    color[1],
                    color[2],
                )
            )
            tris.append(
                (
                    [
                        idy + (idx + 1) * samples[0],
                        idy + 1 + idx * samples[0],
                        idy + 1 + (idx + 1) * samples[0],
                    ],
                    color[0],
                    color[1],
                    color[2],
                )
            )
    tris = np_ply.asarray(
        tris,
        dtype=[
            ("vertex_indices", "i4", (3,)),
            ("red", "u1"),
            ("green", "u1"),
            ("blue", "u1"),
        ],
    )
    pnts = np_ply.asarray(pnts, dtype=[("x", "f4"), ("y", "f4"), ("z", "f4")])
    # Save mesh.
    el1 = PlyElement.describe(pnts, "vertex", comments=["Vertex data"])
    el2 = PlyElement.describe(tris, "face", comments=["Face data"])
    PlyData([el1, el2], text="True").write(safe_path)

write_to_text_file(content, filename, write_flag='w')

Defininition to write a Pythonic list to a text file.

Parameters:

  • content
              Pythonic string list to be written to a file.
    
  • filename
              Destination filename (i.e. test.txt).
    
  • write_flag
              Defines the interaction with the file.
              The default is "w" (overwrite any existing content).
              For more see: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
    

Returns:

  • bool ( True if successful. ) –
Source code in odak/tools/file.py
def write_to_text_file(content, filename, write_flag="w"):
    """
    Defininition to write a Pythonic list to a text file.


    Parameters
    ----------
    content         : list
                      Pythonic string list to be written to a file.
    filename        : str
                      Destination filename (i.e. test.txt).
    write_flag      : str
                      Defines the interaction with the file.
                      The default is "w" (overwrite any existing content).
                      For more see: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

    Returns
    -------
    bool           : True if successful.
    """
    allowed_write_flags = {"w", "a", "x", "r+", "w+", "a+"}
    if write_flag not in allowed_write_flags:
        raise ValueError(
            f"Write flag must be one of {allowed_write_flags}, got '{write_flag}'"
        )
    safe_path = validate_path(filename)
    with open(safe_path, write_flag) as f:
        for line in content:
            f.write("{}\n".format(line))
    return True

zero_pad(field, size=None, method='center')

Definition to zero pad a MxN array to 2Mx2N array.

Parameters:

  • field
                Input field MxN array.
    
  • size
                Size to be zeropadded.
    
  • method
                Zeropad either by placing the content to center or to the left.
    

Returns:

  • field_zero_padded ( ndarray ) –

    Zeropadded version of the input field.

Source code in odak/tools/matrix.py
def zero_pad(field, size=None, method="center"):
    """
    Definition to zero pad a MxN array to 2Mx2N array.

    Parameters
    ----------
    field             : ndarray
                        Input field MxN array.
    size              : list
                        Size to be zeropadded.
    method            : str
                        Zeropad either by placing the content to center or to the left.

    Returns
    ----------
    field_zero_padded : ndarray
                        Zeropadded version of the input field.
    """
    if type(size) == type(None):
        hx = int(np.ceil(field.shape[0]) / 2)
        hy = int(np.ceil(field.shape[1]) / 2)
    else:
        hx = int(np.ceil((size[0] - field.shape[0]) / 2))
        hy = int(np.ceil((size[1] - field.shape[1]) / 2))
    if method == "center":
        field_zero_padded = np.pad(field, ([hx, hx], [hy, hy]), constant_values=(0, 0))
    elif method == "left aligned":
        field_zero_padded = np.pad(
            field, ([0, 2 * hx], [0, 2 * hy]), constant_values=(0, 0)
        )
    if type(size) != type(None):
        field_zero_padded = field_zero_padded[0 : size[0], 0 : size[1]]
    return field_zero_padded

read_PLY(fn, offset=[0, 0, 0], angles=[0.0, 0.0, 0.0], mode='XYZ')

Definition to read a PLY file and extract meshes from a given PLY file. Note that rotation is always with respect to 0,0,0.

Parameters:

  • fn
           Filename of a PLY file.
    
  • offset
           Offset in X,Y,Z.
    
  • angles
           Rotation angles in degrees.
    
  • mode
           Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    

Returns:

  • triangles ( ndarray ) –

    Triangles from a given PLY file. Note that the triangles coming out of this function isn't always structured in the right order and with the size of (MxN)x3. You can use numpy's reshape to restructure it to mxnx3 if you know what you are doing.

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If fn is not a string.
Source code in odak/tools/asset.py
def read_PLY(fn, offset=[0, 0, 0], angles=[0.0, 0.0, 0.0], mode="XYZ"):
    """
    Definition to read a PLY file and extract meshes from a given PLY file. Note that rotation is always with respect to 0,0,0.

    Parameters
    ----------
    fn           : string
                   Filename of a PLY file.
    offset       : ndarray
                   Offset in X,Y,Z.
    angles       : list
                   Rotation angles in degrees.
    mode         : str
                   Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.

    Returns
    ----------
    triangles    : ndarray
                   Triangles from a given PLY file. Note that the triangles coming out of this function isn't always structured in the right order and with the size of (MxN)x3. You can use numpy's reshape to restructure it to mxnx3 if you know what you are doing.

    Raises
    ------
    ValueError   : If path validation fails or extension is not allowed.
    TypeError    : If fn is not a string.
    """
    if np.__name__ != "numpy":
        import numpy as np_ply
    else:
        np_ply = np
    safe_path = validate_path(fn, allowed_extensions=[".ply"])
    with open(safe_path, "rb") as f:
        plydata = PlyData.read(f)
    triangle_ids = np_ply.vstack(plydata["face"].data["vertex_indices"])
    triangles = []
    for vertex_ids in triangle_ids:
        triangle = [
            rotate_point(
                plydata["vertex"][int(vertex_ids[0])].tolist(),
                angles=angles,
                offset=offset,
            )[0],
            rotate_point(
                plydata["vertex"][int(vertex_ids[1])].tolist(),
                angles=angles,
                offset=offset,
            )[0],
            rotate_point(
                plydata["vertex"][int(vertex_ids[2])].tolist(),
                angles=angles,
                offset=offset,
            )[0],
        ]
        triangle = np_ply.asarray(triangle)
        triangles.append(triangle)
    triangles = np_ply.array(triangles)
    triangles = np.asarray(triangles, dtype=np.float32)
    return triangles

read_PLY_point_cloud(filename)

Definition to read a PLY file as a point cloud.

Parameters:

  • filename
           Filename of a PLY file.
    

Returns:

  • point_cloud ( ndarray ) –

    An array filled with poitns from the PLY file.

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If filename is not a string.
Source code in odak/tools/asset.py
def read_PLY_point_cloud(filename):
    """
    Definition to read a PLY file as a point cloud.

    Parameters
    ----------
    filename     : str
                   Filename of a PLY file.

    Returns
    ----------
    point_cloud  : ndarray
                   An array filled with poitns from the PLY file.

    Raises
    ------
    ValueError   : If path validation fails or extension is not allowed.
    TypeError    : If filename is not a string.
    """
    safe_path = validate_path(filename, allowed_extensions=[".ply"])
    plydata = PlyData.read(safe_path)
    if np.__name__ != "numpy":
        import numpy as np_ply

        point_cloud = np_ply.zeros((plydata["vertex"][:].shape[0], 3))
        point_cloud[:, 0] = np_ply.asarray(plydata["vertex"]["x"][:])
        point_cloud[:, 1] = np_ply.asarray(plydata["vertex"]["y"][:])
        point_cloud[:, 2] = np_ply.asarray(plydata["vertex"]["z"][:])
        point_cloud = np.asarray(point_cloud)
    else:
        point_cloud = np.zeros((plydata["vertex"][:].shape[0], 3))
        point_cloud[:, 0] = np.asarray(plydata["vertex"]["x"][:])
        point_cloud[:, 1] = np.asarray(plydata["vertex"]["y"][:])
        point_cloud[:, 2] = np.asarray(plydata["vertex"]["z"][:])
    return point_cloud

write_PLY(triangles, savefn='output.ply')

Definition to generate a PLY file from given points.

Parameters:

  • triangles
          List of triangles with the size of Mx3x3.
    
  • savefn
          Filename for a PLY file.
    

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If savefn is not a string.
Source code in odak/tools/asset.py
def write_PLY(triangles, savefn="output.ply"):
    """
    Definition to generate a PLY file from given points.

    Parameters
    ----------
    triangles   : ndarray
                  List of triangles with the size of Mx3x3.
    savefn      : string
                  Filename for a PLY file.

    Raises
    ------
    ValueError  : If path validation fails or extension is not allowed.
    TypeError   : If savefn is not a string.
    """
    safe_path = validate_path(savefn, allowed_extensions=[".ply"])
    tris = []
    pnts = []
    color = [255, 255, 255]
    for tri_id in range(triangles.shape[0]):
        tris.append(
            ([3 * tri_id, 3 * tri_id + 1, 3 * tri_id + 2], color[0], color[1], color[2])
        )
        for i in range(0, 3):
            pnts.append(
                (
                    float(triangles[tri_id][i][0]),
                    float(triangles[tri_id][i][1]),
                    float(triangles[tri_id][i][2]),
                )
            )
    tris = np.asarray(
        tris,
        dtype=[
            ("vertex_indices", "i4", (3,)),
            ("red", "u1"),
            ("green", "u1"),
            ("blue", "u1"),
        ],
    )
    pnts = np.asarray(pnts, dtype=[("x", "f4"), ("y", "f4"), ("z", "f4")])
    # Save mesh.
    el1 = PlyElement.describe(pnts, "vertex", comments=["Vertex data"])
    el2 = PlyElement.describe(tris, "face", comments=["Face data"])
    PlyData([el1, el2], text="True").write(safe_path)

write_PLY_from_points(points, savefn='output.ply')

Definition to generate a PLY file from given points.

Parameters:

  • points
          List of points with the size of MxNx3.
    
  • savefn
          Filename for a PLY file.
    

Raises:

  • ValueError : If path validation fails or extension is not allowed.
  • TypeError : If savefn is not a string.
Source code in odak/tools/asset.py
def write_PLY_from_points(points, savefn="output.ply"):
    """
    Definition to generate a PLY file from given points.

    Parameters
    ----------
    points      : ndarray
                  List of points with the size of MxNx3.
    savefn      : string
                  Filename for a PLY file.

    Raises
    ------
    ValueError  : If path validation fails or extension is not allowed.
    TypeError   : If savefn is not a string.
    """
    safe_path = validate_path(savefn, allowed_extensions=[".ply"])
    if np.__name__ != "numpy":
        import numpy as np_ply
    else:
        np_ply = np
    # Generate equation
    samples = [points.shape[0], points.shape[1]]
    # Generate vertices.
    pnts = []
    tris = []
    for idx in range(0, samples[0]):
        for idy in range(0, samples[1]):
            pnt = (points[idx, idy, 0], points[idx, idy, 1], points[idx, idy, 2])
            pnts.append(pnt)
    color = [255, 255, 255]
    for idx in range(0, samples[0] - 1):
        for idy in range(0, samples[1] - 1):
            tris.append(
                (
                    [
                        idy + (idx + 1) * samples[0],
                        idy + idx * samples[0],
                        idy + 1 + idx * samples[0],
                    ],
                    color[0],
                    color[1],
                    color[2],
                )
            )
            tris.append(
                (
                    [
                        idy + (idx + 1) * samples[0],
                        idy + 1 + idx * samples[0],
                        idy + 1 + (idx + 1) * samples[0],
                    ],
                    color[0],
                    color[1],
                    color[2],
                )
            )
    tris = np_ply.asarray(
        tris,
        dtype=[
            ("vertex_indices", "i4", (3,)),
            ("red", "u1"),
            ("green", "u1"),
            ("blue", "u1"),
        ],
    )
    pnts = np_ply.asarray(pnts, dtype=[("x", "f4"), ("y", "f4"), ("z", "f4")])
    # Save mesh.
    el1 = PlyElement.describe(pnts, "vertex", comments=["Vertex data"])
    el2 = PlyElement.describe(tris, "face", comments=["Face data"])
    PlyData([el1, el2], text="True").write(safe_path)

convert_to_numpy(a)

A definition to convert Torch to Numpy.

Parameters:

  • a
         Input Torch array.
    

Returns:

  • b ( ndarray ) –

    Converted array.

Source code in odak/tools/conversions.py
def convert_to_numpy(a):
    """
    A definition to convert Torch to Numpy.

    Parameters
    ----------
    a          : torch.Tensor
                 Input Torch array.

    Returns
    ----------
    b          : numpy.ndarray
                 Converted array.
    """
    b = a.to("cpu").detach().numpy()
    return b

convert_to_torch(a, grad=True)

A definition to convert Numpy arrays to Torch.

Parameters:

  • a
         Input Numpy array.
    
  • grad
         Set if the converted array requires gradient.
    

Returns:

  • c ( Tensor ) –

    Converted array.

Source code in odak/tools/conversions.py
def convert_to_torch(a, grad=True):
    """
    A definition to convert Numpy arrays to Torch.

    Parameters
    ----------
    a          : ndarray
                 Input Numpy array.
    grad       : bool
                 Set if the converted array requires gradient.

    Returns
    ----------
    c          : torch.Tensor
                 Converted array.
    """
    b = np.copy(a)
    c = torch.from_numpy(b)
    c.requires_grad_(grad)
    return c

check_directory(directory, validate=True)

Definition to check if a directory exist. If it doesn't exist, this definition will create one.

Parameters:

  • directory
            Full directory path.
    
  • validate
            Whether to validate the path for security (default: True).
            When True, checks for path traversal, null bytes, and other unsafe patterns.
    

Returns:

  • bool ( bool ) –

    Returns True if directory already exists, False if created.

Raises:

  • ValueError : If path traversal attempt detected, invalid characters found,

    or directory creation fails due to permissions/invalid path.

  • TypeError : If directory is not a string.
Source code in odak/tools/file.py
def check_directory(directory, validate=True):
    """
    Definition to check if a directory exist. If it doesn't exist, this definition will create one.

    Parameters
    ----------
    directory     : str
                    Full directory path.
    validate      : bool
                    Whether to validate the path for security (default: True).
                    When True, checks for path traversal, null bytes, and other unsafe patterns.

    Returns
    -------
    bool         :  bool
                   Returns True if directory already exists, False if created.

    Raises
    ------
    ValueError   : If path traversal attempt detected, invalid characters found,
                   or directory creation fails due to permissions/invalid path.
    TypeError    : If directory is not a string.
    """
    if validate:
        logger.debug("Checking directory: {}".format(directory))
        safe_path = validate_path(directory + "/")
    else:
        # Bypass validation for internal use only
        safe_path = os.path.abspath(os.path.expanduser(directory))

    if not os.path.exists(safe_path):
        try:
            os.makedirs(safe_path)
            logger.info("Created directory: {}".format(safe_path))
            return False
        except Exception as e:
            raise ValueError(f"Failed to create directory '{safe_path}': {str(e)}")

    # Verify it's actually a directory, not a file with the same name
    if not os.path.isdir(safe_path):
        raise ValueError(f"Path exists but is not a directory: {safe_path}")

    logger.info("Directory already exists: {}".format(safe_path))
    return True

convert_bytes(num)

A definition to convert bytes to semantic scheme (MB,GB or alike). Inspired from https://stackoverflow.com/questions/2104080/how-can-i-check-file-size-in-python#2104083.

Parameters:

  • num
         Size in bytes
    

Returns:

  • num ( float ) –

    Size in new unit.

  • x ( str ) –

    New unit bytes, KB, MB, GB or TB.

Source code in odak/tools/file.py
def convert_bytes(num):
    """
    A definition to convert bytes to semantic scheme (MB,GB or alike). Inspired from https://stackoverflow.com/questions/2104080/how-can-i-check-file-size-in-python#2104083.


    Parameters
    ----------
    num        : float
                 Size in bytes


    Returns
    ----------
    num        : float
                 Size in new unit.
    x          : str
                 New unit bytes, KB, MB, GB or TB.
    """
    for x in ["bytes", "KB", "MB", "GB", "TB"]:
        if num < 1024.0:
            return num, x
        num /= 1024.0
    return None, None

copy_file(source, destination, follow_symlinks=True)

Definition to copy a file from one location to another.

Parameters:

  • source
              Source filename.
    
  • destination
              Destination filename.
    
  • follow_symlinks (bool, default: True ) –
              Set to True to follow the source of symbolic links.
    

Returns:

  • str ( The absolute path of the destination file on success. ) –

Raises:

  • ValueError : If source doesn't exist, destination is a directory,

    or copy fails due to permissions/disk space.

  • TypeError : If source or destination are not strings.
Source code in odak/tools/file.py
def copy_file(source, destination, follow_symlinks=True):
    """
    Definition to copy a file from one location to another.

    Parameters
    ----------
    source          : str
                      Source filename.
    destination     : str
                      Destination filename.
    follow_symlinks : bool
                      Set to True to follow the source of symbolic links.

    Returns
    -------
    str            : The absolute path of the destination file on success.

    Raises
    ------
    ValueError     : If source doesn't exist, destination is a directory,
                     or copy fails due to permissions/disk space.
    TypeError      : If source or destination are not strings.
    """
    # Validate both paths for security (path traversal, null bytes, etc.)
    safe_source = validate_path(source)
    safe_destination = validate_path(destination)

    # Verify source file exists
    if not os.path.exists(safe_source):
        raise ValueError(f"Source file does not exist: {safe_source}")

    # Verify source is a file, not a directory
    if not os.path.isfile(safe_source):
        raise ValueError(f"Source is not a file: {safe_source}")

    # Check if destination is an existing directory
    if os.path.isdir(safe_destination):
        raise ValueError(f"Destination is a directory, not a file: {safe_destination}")

    try:
        # Use copy2 to preserve file metadata (timestamps, permissions)
        return shutil.copy2(safe_source, safe_destination)
    except OSError as e:
        raise ValueError(
            f"Failed to copy '{safe_source}' to '{safe_destination}': {str(e)}"
        )

expanduser(filename)

Definition to decode filename using namespaces and shortcuts.

Parameters:

  • filename
            Filename.
    

Returns:

  • new_filename ( str ) –

    Filename.

Source code in odak/tools/file.py
def expanduser(filename):
    """
    Definition to decode filename using namespaces and shortcuts.


    Parameters
    ----------
    filename      : str
                    Filename.


    Returns
    -------
    new_filename  : str
                    Filename.
    """
    new_filename = os.path.expanduser(filename)
    return new_filename

get_base_filename(filename)

Definition to retrieve the base filename and extension type.

Parameters:

  • filename
             Input filename.
    

Returns:

  • basename ( str ) –

    Basename extracted from the filename.

  • extension ( str ) –

    Extension extracted from the filename.

Source code in odak/tools/file.py
def get_base_filename(filename):
    """
    Definition to retrieve the base filename and extension type.


    Parameters
    ----------
    filename       : str
                     Input filename.


    Returns
    -------
    basename       : str
                     Basename extracted from the filename.
    extension      : str
                     Extension extracted from the filename.
    """
    cache = os.path.basename(filename)
    basename = os.path.splitext(cache)[0]
    extension = os.path.splitext(cache)[1]
    return basename, extension

list_directories(path, recursive=True)

Lists directories inside a given directory, recursively if specified.

Parameters:

  • path
        The path to the directory you want to list.
    
  • recursive (bool, default: True ) –
        If True, lists subdirectories recursively. Defaults to True.
    

Returns:

  • list

    A list of directory names.

Source code in odak/tools/file.py
def list_directories(path, recursive=True):
    """
    Lists directories inside a given directory, recursively if specified.

    Parameters
    ----------
    path      : str
                The path to the directory you want to list.
    recursive : bool, optional
                If True, lists subdirectories recursively. Defaults to True.

    Returns
    -------
    list
                A list of directory names.
    """
    directories = []
    safe_path = validate_path(path + "/")
    if recursive:
        for entry in os.listdir(safe_path):
            full_path = os.path.join(safe_path, entry)
            if os.path.isdir(full_path):
                directories.append(entry)
                directories.extend(list_directories(full_path, recursive=True))
    else:
        contents = os.listdir(safe_path)
        directories = [f for f in contents if os.path.isdir(os.path.join(safe_path, f))]
    return sorted(directories)

list_files(path, key='*.*', recursive=True)

Definition to list files in a given path with a given key.

Parameters:

  • path
          Path to a folder.
    
  • key
          Key used for scanning a path.
    
  • recursive
          If set True, scan the path recursively.
    

Returns:

  • files_list ( ndarray ) –

    list of files found in a given path.

Source code in odak/tools/file.py
def list_files(path, key="*.*", recursive=True):
    """
    Definition to list files in a given path with a given key.


    Parameters
    ----------
    path        : str
                  Path to a folder.
    key         : str
                  Key used for scanning a path.
    recursive   : bool
                  If set True, scan the path recursively.


    Returns
    -------
    files_list  : ndarray
                  list of files found in a given path.
    """
    safe_path = validate_path(path + "/")
    search_result = None
    if recursive == True:
        search_result = pathlib.Path(safe_path).rglob(key)
    elif recursive == False:
        search_result = pathlib.Path(safe_path).glob(key)
    if search_result is None:
        return []
    files_list = [str(item) for item in search_result]
    return sorted(files_list)

load_dictionary(filename)

Definition to load a dictionary (JSON) file.

Parameters:

  • filename
            Filename.
    

Returns:

  • settings ( dict ) –

    Dictionary read from the file.

Source code in odak/tools/file.py
def load_dictionary(filename):
    """
    Definition to load a dictionary (JSON) file.


    Parameters
    ----------
    filename      : str
                    Filename.


    Returns
    ----------
    settings      : dict
                    Dictionary read from the file.

    """
    logger.info("Loading dictionary: {}".format(filename))
    safe_path = validate_path(filename, allowed_extensions=[".json"])
    with open(safe_path, "r", encoding="utf-8") as f:
        settings = json.load(f)
    logger.info("Loaded dictionary: {}".format(safe_path))
    return settings

load_image(fn, normalizeby=0.0, torch_style=False)

Definition to load an image from a given location as a Numpy array.

Parameters:

  • fn
            Filename.
    
  • normalizeby
            Value to to normalize images with. Default value of zero will lead to no normalization.
    
  • torch_style
            If set True, it will load an image mxnx3 as 3xmxn.
    

Returns:

  • image ( ndarray ) –

    Image loaded as a Numpy array.

Source code in odak/tools/file.py
def load_image(fn, normalizeby=0.0, torch_style=False):
    """
    Definition to load an image from a given location as a Numpy array.


    Parameters
    ----------
    fn           : str
                    Filename.
    normalizeby  : float
                    Value to to normalize images with. Default value of zero will lead to no normalization.
    torch_style  : bool
                    If set True, it will load an image mxnx3 as 3xmxn.


    Returns
    ----------
    image        :  ndarray
                    Image loaded as a Numpy array.

    """
    logger.info("Loading image: {}".format(fn))
    safe_path = validate_path(
        fn,
        allowed_extensions=[
            ".png",
            ".jpg",
            ".jpeg",
            ".bmp",
            ".tiff",
            ".tif",
            ".gif",
            ".webp",
            ".pbm",
            ".pgm",
            ".ppm",
            ".sr",
            ".ras",
        ],
    )
    image = cv2.imread(safe_path, cv2.IMREAD_UNCHANGED)
    if isinstance(image, type(None)):
        raise ValueError(
            f"Failed to load image from '{safe_path}'. "
            f"Check file format, permissions, and that the file exists."
        )
    if len(image.shape) > 2:
        new_image = np.copy(image)
        new_image[:, :, 0] = image[:, :, 2]
        new_image[:, :, 2] = image[:, :, 0]
        image = new_image
    if normalizeby != 0.0:
        image = image * 1.0 / normalizeby
    if torch_style == True and len(image.shape) > 2:
        image = np.moveaxis(image, -1, 0)
    logger.info("Loaded image: {}".format(safe_path))
    return image.astype(float)

read_text_file(filename)

Definition to read a given text file and convert it into a Pythonic list.

Parameters:

  • filename
              Source filename (i.e. test.txt).
    

Returns:

  • content ( list ) –

    Pythonic string list containing the text from the file provided.

Raises:

  • ValueError : If path validation fails or unsafe characters detected.
Source code in odak/tools/file.py
def read_text_file(filename):
    """
    Definition to read a given text file and convert it into a Pythonic list.


    Parameters
    ----------
    filename        : str
                      Source filename (i.e. test.txt).


    Returns
    -------
    content         : list
                      Pythonic string list containing the text from the file provided.

    Raises
    ------
    ValueError     : If path validation fails or unsafe characters detected.
    """
    content = []
    safe_path = validate_path(filename)
    with open(safe_path, "r", encoding="utf-8") as f:
        for line in f:
            content.append(line.rstrip())
    return content

resize_image(img, target_size)

Definition to resize a given image to a target shape.

Parameters:

  • img
            MxN image to be resized.
            Image must be normalized (0-1).
    
  • target_size
            Target shape.
    

Returns:

  • img ( ndarray ) –

    Resized image.

Source code in odak/tools/file.py
def resize_image(img, target_size):
    """
    Definition to resize a given image to a target shape.


    Parameters
    ----------
    img           : ndarray
                    MxN image to be resized.
                    Image must be normalized (0-1).
    target_size   : list
                    Target shape.


    Returns
    ----------
    img           : ndarray
                    Resized image.

    """
    logger.debug("Resizing image to {}".format(target_size))
    img = cv2.resize(
        img, dsize=(target_size[0], target_size[1]), interpolation=cv2.INTER_AREA
    )
    logger.debug("Image resized to {}".format(target_size))
    return img

save_dictionary(settings, filename)

Definition to load a dictionary (JSON) file.

Parameters:

  • settings
            Dictionary read from the file.
    
  • filename
            Filename.
    
Source code in odak/tools/file.py
def save_dictionary(settings, filename):
    """
    Definition to load a dictionary (JSON) file.


    Parameters
    ----------
    settings      : dict
                    Dictionary read from the file.
    filename      : str
                    Filename.
    """
    logger.info("Saving dictionary: {}".format(filename))
    safe_path = validate_path(filename, allowed_extensions=[".json"])
    with open(safe_path, "w", encoding="utf-8") as f:
        json.dump(settings, f, ensure_ascii=False, indent=4)
    logger.info("Saved dictionary: {}".format(safe_path))
    return settings

save_image(fn, img, cmin=0, cmax=255, color_depth=8)

Definition to save a Numpy array as an image.

Parameters:

  • fn
            Filename.
    
  • img
            A numpy array with NxMx3 or NxMx1 shapes.
    
  • cmin
            Minimum value that will be interpreted as 0 level in the final image.
    
  • cmax
            Maximum value that will be interpreted as 255 level in the final image.
    
  • color_depth
            Pixel color depth in bits, default is eight bits.
    

Returns:

  • bool ( bool ) –

    True if successful.

Source code in odak/tools/file.py
def save_image(fn, img, cmin=0, cmax=255, color_depth=8):
    """
    Definition to save a Numpy array as an image.


    Parameters
    ----------
    fn           : str
                    Filename.
    img          : ndarray
                    A numpy array with NxMx3 or NxMx1 shapes.
    cmin         : int
                    Minimum value that will be interpreted as 0 level in the final image.
    cmax         : int
                    Maximum value that will be interpreted as 255 level in the final image.
    color_depth  : int
                    Pixel color depth in bits, default is eight bits.


    Returns
    ----------
    bool         :  bool
                    True if successful.

    """
    logger.info("Saving image: {}".format(fn))
    input_img = np.copy(img).astype(np.float32)
    cmin = float(cmin)
    cmax = float(cmax)
    input_img[input_img < cmin] = cmin
    input_img[input_img > cmax] = cmax
    input_img /= cmax
    input_img = input_img * 1.0 * (2**color_depth - 1)
    input_img = np.nan_to_num(input_img, nan=0.0, posinf=cmax, neginf=cmin)
    if color_depth == 8:
        input_img = input_img.astype(np.uint8)
    elif color_depth == 16:
        input_img = input_img.astype(np.uint16)
    if len(input_img.shape) > 2:
        if input_img.shape[2] > 1:
            cache_img = np.copy(input_img)
            cache_img[:, :, 0] = input_img[:, :, 2]
            cache_img[:, :, 2] = input_img[:, :, 0]
            input_img = cache_img
    safe_path = validate_path(
        fn, allowed_extensions=[".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"]
    )
    cv2.imwrite(safe_path, input_img)
    logger.info("Saved image: {}".format(safe_path))
    return True

shell_command(cmd, cwd='.', timeout=None, check=True)

Definition to initiate shell commands securely.

Parameters:

  • cmd
           Command to be executed as a list of arguments.
           Example: ['blender', '-b', 'file.blend']
    
  • cwd
           Working directory. Default is current directory.
    
  • timeout
           Timeout in seconds if the process isn't complete.
           If None, no timeout is enforced.
    
  • check
           Set it to True to return results and enable timeout. False returns only process.
    

Returns:

  • proc ( Popen ) –

    Generated process handle.

  • outs ( str or bytes ) –

    Standard output of the executed command (None when check=False).

  • errs ( str or bytes ) –

    Standard error of the executed command (None when check=False).

Raises:

  • ValueError : If command contains dangerous characters, forbidden commands,

    null bytes, or if working directory path is invalid.

  • TypeError : If cmd is not a list or cwd is not a string.
  • subprocess.TimeoutExpired : If process exceeds timeout (when check=True).
Security Features
  • Command whitelist validation (blender, dispynode.py, ffmpeg, python, etc.)
  • Blocks shell metacharacters (; & | ` $ < > quotes)
  • Null byte injection protection
  • Path traversal blocked in working directory
  • Uses Popen with shell=False for safe execution
Example

proc, outs, errs = shell_command(['blender', '-b', 'scene.blend']) proc, None, None = shell_command(['python', 'script.py'], check=False)

Source code in odak/tools/file.py
def shell_command(cmd, cwd=".", timeout=None, check=True):
    """
    Definition to initiate shell commands securely.

    Parameters
    ----------
    cmd          : list
                   Command to be executed as a list of arguments.
                   Example: ['blender', '-b', 'file.blend']
    cwd          : str
                   Working directory. Default is current directory.
    timeout      : int or None
                   Timeout in seconds if the process isn't complete.
                   If None, no timeout is enforced.
    check        : bool
                   Set it to True to return results and enable timeout. False returns only process.

    Returns
    -------
    proc         : subprocess.Popen
                   Generated process handle.
    outs         : str or bytes
                   Standard output of the executed command (None when check=False).
    errs         : str or bytes
                   Standard error of the executed command (None when check=False).

    Raises
    ------
    ValueError   : If command contains dangerous characters, forbidden commands,
                   null bytes, or if working directory path is invalid.
    TypeError    : If cmd is not a list or cwd is not a string.
    subprocess.TimeoutExpired : If process exceeds timeout (when check=True).

    Security Features
    ---------------
    - Command whitelist validation (blender, dispynode.py, ffmpeg, python, etc.)
    - Blocks shell metacharacters (; & | ` $ < > quotes)
    - Null byte injection protection
    - Path traversal blocked in working directory
    - Uses Popen with shell=False for safe execution

    Example
    ------
    >>> proc, outs, errs = shell_command(['blender', '-b', 'scene.blend'])
    >>> proc, None, None = shell_command(['python', 'script.py'], check=False)
    """
    # Validate command arguments
    validated_cmd = validate_shell_command(cmd)

    # Validate working directory
    safe_cwd = validate_cwd(cwd if isinstance(cwd, str) else ".")

    # Execute with shell=False for security (prevents shell injection)
    proc = subprocess.Popen(
        validated_cmd,
        cwd=safe_cwd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        shell=False,  # Critical: Prevents shell metacharacter injection
    )

    if not check:
        return proc, None, None

    try:
        outs, errs = proc.communicate(timeout=timeout)
    except subprocess.TimeoutExpired:
        proc.kill()
        outs, errs = proc.communicate()

    return proc, outs, errs

size_of_a_file(file_path)

A definition to get size of a file with a relevant unit.

Parameters:

  • file_path
         Path of the file.
    

Returns:

  • a ( float ) –

    Size of the file.

  • b ( str ) –

    Unit of the size (bytes, KB, MB, GB or TB).

Source code in odak/tools/file.py
def size_of_a_file(file_path):
    """
    A definition to get size of a file with a relevant unit.


    Parameters
    ----------
    file_path  : float
                 Path of the file.


    Returns
    ----------
    a          : float
                 Size of the file.
    b          : str
                 Unit of the size (bytes, KB, MB, GB or TB).
    """
    if os.path.isfile(file_path):
        file_info = os.stat(file_path)
        a, b = convert_bytes(file_info.st_size)
        return a, b
    return None, None

validate_cwd(cwd)

Validates working directory path.

Parameters:

  • cwd
              Working directory path.
    

Returns:

  • safe_cwd ( str ) –

    The validated and absolute path.

Raises:

  • ValueError : If path contains dangerous characters.
Source code in odak/tools/file.py
def validate_cwd(cwd):
    """
    Validates working directory path.

    Parameters
    ----------
    cwd             : str
                      Working directory path.

    Returns
    -------
    safe_cwd        : str
                      The validated and absolute path.

    Raises
    ------
    ValueError      : If path contains dangerous characters.
    """
    if not isinstance(cwd, str):
        raise TypeError(f"Working directory must be a string, got {type(cwd).__name__}")

    if "\x00" in cwd:
        raise ValueError("Null bytes detected in working directory path")

    expanded = os.path.expanduser(cwd)
    absolute_path = os.path.abspath(expanded)

    # Check if the directory exists (optional, can be removed if you want to allow non-existent dirs)
    # if not os.path.isdir(absolute_path):
    #     raise ValueError(f"Working directory does not exist: {absolute_path}")

    return absolute_path

validate_path(path, allowed_extensions=None)

Validates a file path for security safety.

Parameters:

  • path
              Path to validate.
    
  • allowed_extensions (list, default: None ) –
                  List of allowed extensions (e.g., ['.png', '.jpg']).
                  If None, all extensions are allowed.
    

Returns:

  • safe_path ( str ) –

    The validated and secured path (with tilde expanded).

Raises:

  • ValueError : If path traversal attempt detected or extension not allowed.
  • TypeError : If path is not a string.
Source code in odak/tools/file.py
def validate_path(path, allowed_extensions=None):
    """
    Validates a file path for security safety.

    Parameters
    ----------
    path            : str
                      Path to validate.
    allowed_extensions : list, optional
                          List of allowed extensions (e.g., ['.png', '.jpg']).
                          If None, all extensions are allowed.

    Returns
    -------
    safe_path       : str
                      The validated and secured path (with tilde expanded).

    Raises
    ------
    ValueError      : If path traversal attempt detected or extension not allowed.
    TypeError       : If path is not a string.
    """
    if not isinstance(path, str):
        raise TypeError(f"Path must be a string, got {type(path).__name__}")

    # Check for null bytes before expanding user (Windows path injection)
    if "\x00" in path:
        raise ValueError("Null bytes not allowed in path")

    # Check for path traversal patterns BEFORE expanding
    if ".." in path.split(os.sep) or ".." in path.replace(os.sep, "/").split("/"):
        if re.search(r"(^|[/\\])\.\.([/\\]|$)", path):
            raise ValueError("Path traversal detected: '..' not allowed in path")

    # Check for URL protocols before expanding
    path_lower = path.lower()
    if re.search(r"https?://|ftp://", path_lower):
        raise ValueError("URL protocols not allowed in file paths")

    path = os.path.expanduser(path)
    resolved_path = os.path.abspath(path)

    # Check for UNC or device paths on Windows
    if re.match(r"\\\\\\\|\\\\\\?\.\\", path) or path.startswith("//."):
        raise ValueError("UNC/device paths not allowed")

    if len(resolved_path) > 260:  # Windows MAX_PATH limit
        raise ValueError("Path exceeds maximum allowed length (260 characters)")

    if allowed_extensions is not None:
        _, file_ext = os.path.splitext(path)
        ext_lower = file_ext.lower()
        allowed_normalized = [
            ext.lower() if ext.startswith(".") else f".{ext}"
            for ext in allowed_extensions
        ]
        if ext_lower not in allowed_normalized:
            raise ValueError(
                f"File extension '{file_ext}' is not allowed. "
                f"Allowed: {allowed_extensions}"
            )

    logger.debug(f"Path validated: {path}")
    return resolved_path

validate_shell_command(cmd_list)

Validates shell command arguments for security.

Parameters:

  • cmd_list
              List of command arguments to validate.
    

Returns:

  • validated_list ( list ) –

    The validated and sanitized command list.

Raises:

  • ValueError : If command contains dangerous characters or is not allowed.
  • TypeError : If cmd_list is not a list.
Source code in odak/tools/file.py
def validate_shell_command(cmd_list):
    """
    Validates shell command arguments for security.

    Parameters
    ----------
    cmd_list        : list
                      List of command arguments to validate.

    Returns
    -------
    validated_list  : list
                      The validated and sanitized command list.

    Raises
    ------
    ValueError      : If command contains dangerous characters or is not allowed.
    TypeError       : If cmd_list is not a list.
    """
    if not isinstance(cmd_list, list):
        raise TypeError(f"Command must be a list, got {type(cmd_list).__name__}")

    if len(cmd_list) == 0:
        raise ValueError("Command list cannot be empty")

    validated = []
    for idx, arg in enumerate(cmd_list):
        if not isinstance(arg, str):
            raise TypeError(
                f"Command argument {idx} must be a string, got {type(arg).__name__}"
            )

        # Check for null bytes
        if "\x00" in arg:
            raise ValueError(f"Null bytes detected in command argument {idx}")

        # Check for dangerous shell metacharacters
        for pattern in DANGEROUS_PATTERNS:
            if re.search(pattern, arg):
                raise ValueError(
                    f"Dangerous character detected in command argument {idx}: '{arg[:50]}...'. "
                    f"Shell metacharacters are not allowed."
                )

        # Check if the base command (first argument) is in whitelist
        if idx == 0:
            base_cmd = os.path.basename(arg).lower()
            if base_cmd not in ALLOWED_COMMANDS and not arg.startswith("/"):
                # Allow absolute paths, but warn
                logger.warning(
                    f"Command '{base_cmd}' is not in the allowed whitelist. "
                    f"Allowed commands: {sorted(ALLOWED_COMMANDS)}"
                )

        validated.append(arg)

    logger.debug(f"Shell command validated successfully")
    return validated

write_to_text_file(content, filename, write_flag='w')

Defininition to write a Pythonic list to a text file.

Parameters:

  • content
              Pythonic string list to be written to a file.
    
  • filename
              Destination filename (i.e. test.txt).
    
  • write_flag
              Defines the interaction with the file.
              The default is "w" (overwrite any existing content).
              For more see: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
    

Returns:

  • bool ( True if successful. ) –
Source code in odak/tools/file.py
def write_to_text_file(content, filename, write_flag="w"):
    """
    Defininition to write a Pythonic list to a text file.


    Parameters
    ----------
    content         : list
                      Pythonic string list to be written to a file.
    filename        : str
                      Destination filename (i.e. test.txt).
    write_flag      : str
                      Defines the interaction with the file.
                      The default is "w" (overwrite any existing content).
                      For more see: https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

    Returns
    -------
    bool           : True if successful.
    """
    allowed_write_flags = {"w", "a", "x", "r+", "w+", "a+"}
    if write_flag not in allowed_write_flags:
        raise ValueError(
            f"Write flag must be one of {allowed_write_flags}, got '{write_flag}'"
        )
    safe_path = validate_path(filename)
    with open(safe_path, write_flag) as f:
        for line in content:
            f.write("{}\n".format(line))
    return True

latex

A class to work with latex documents.

Source code in odak/tools/latex.py
class latex:
    """
    A class to work with latex documents.
    """

    def __init__(self, filename):
        """
        Parameters
        ----------
        filename     : str
                       Source filename (i.e. sample.tex).
        """
        self.filename = filename
        self.content = read_text_file(self.filename)
        self.content_type = []
        self.latex_dictionary = [
            "\\documentclass",
            "\\if",
            "\\pdf",
            "\\else",
            "\\fi",
            "\\vgtc",
            "\\teaser",
            "\\abstract",
            "\\CCS",
            "\\usepackage",
            "\\PassOptionsToPackage",
            "\\definecolor",
            "\\AtBeginDocument",
            "\\providecommand",
            "\\setcopyright",
            "\\copyrightyear",
            "\\acmYear",
            "\\citestyle",
            "\\newcommand",
            "\\acmDOI",
            "\\newabbreviation",
            "\\global",
            "\\begin{document}",
            "\\author",
            "\\affiliation",
            "\\email",
            "\\institution",
            "\\streetaddress",
            "\\city",
            "\\country",
            "\\postcode",
            "\\ccsdesc",
            "\\received",
            "\\includegraphics",
            "\\caption",
            "\\centering",
            "\\label",
            "\\maketitle",
            "\\toprule",
            "\\multirow",
            "\\multicolumn",
            "\\cmidrule",
            "\\addlinespace",
            "\\midrule",
            "\\cellcolor",
            "\\bibliography",
            "}",
            "\\title",
            "</ccs2012>",
            "\\bottomrule",
            "<concept>",
            "<concept",
            "<ccs",
            "\\item",
            "</concept",
            "\\begin{abstract}",
            "\\end{abstract}",
            "\\endinput",
            "\\\\",
        ]
        self.latex_begin_dictionary = [
            "\\begin{figure}",
            "\\begin{figure*}",
            "\\begin{equation}",
            "\\begin{CCSXML}",
            "\\begin{teaserfigure}",
            "\\begin{table*}",
            "\\begin{table}",
            "\\begin{gather}",
            "\\begin{align}",
        ]
        self.latex_end_dictionary = [
            "\\end{figure}",
            "\\end{figure*}",
            "\\end{equation}",
            "\\end{CCSXML}",
            "\\end{teaserfigure}",
            "\\end{table*}",
            "\\end{table}",
            "\\end{gather}",
            "\\end{align}",
        ]
        self._label_lines()

    def set_latex_dictonaries(
        self, begin_dictionary, end_dictionary, syntax_dictionary
    ):
        """
        Set document specific dictionaries so that the lines could be labelled in accordance.


        Parameters
        ----------
        begin_dictionary     : list
                               Pythonic list containing latex syntax for begin commands (i.e. \\begin{align}).
        end_dictionary       : list
                               Pythonic list containing latex syntax for end commands (i.e. \\end{table}).
        syntax_dictionary    : list
                               Pythonic list containing latex syntax (i.e. \\item).

        """
        self.latex_begin_dictionary = begin_dictionary
        self.latex_end_dictionary = end_dictionary
        self.latex_dictionary = syntax_dictionary
        self._label_lines

    def _label_lines(self):
        """
        Internal function for labelling lines.
        """
        content_type_flag = False
        for line_id, line in enumerate(self.content):
            while len(line) > 0 and line[0] == " ":
                line = line[1::]
            self.content[line_id] = line
            if len(line) == 0:
                content_type = "empty"
            elif line[0] == "%":
                content_type = "comment"
            else:
                content_type = "text"
            for syntax in self.latex_begin_dictionary:
                if line.find(syntax) != -1:
                    content_type_flag = True
                    content_type = "latex"
            for syntax in self.latex_dictionary:
                if line.find(syntax) != -1:
                    content_type = "latex"
            if content_type_flag == True:
                content_type = "latex"
                for syntax in self.latex_end_dictionary:
                    if line.find(syntax) != -1:
                        content_type_flag = False
            self.content_type.append(content_type)

    def get_line_count(self):
        """
        Definition to get the line count.


        Returns
        -------
        line_count     : int
                         Number of lines in the loaded latex document.
        """
        self.line_count = len(self.content)
        return self.line_count

    def get_line(self, line_id=0):
        """
        Definition to get a specific line by inputting a line nunber.


        Returns
        ----------
        line           : str
                         Requested line.
        content_type   : str
                         Line's content type (e.g., latex, comment, text).
        """
        line = self.content[line_id]
        content_type = self.content_type[line_id]
        return line, content_type

__init__(filename)

Parameters:

  • filename
           Source filename (i.e. sample.tex).
    
Source code in odak/tools/latex.py
def __init__(self, filename):
    """
    Parameters
    ----------
    filename     : str
                   Source filename (i.e. sample.tex).
    """
    self.filename = filename
    self.content = read_text_file(self.filename)
    self.content_type = []
    self.latex_dictionary = [
        "\\documentclass",
        "\\if",
        "\\pdf",
        "\\else",
        "\\fi",
        "\\vgtc",
        "\\teaser",
        "\\abstract",
        "\\CCS",
        "\\usepackage",
        "\\PassOptionsToPackage",
        "\\definecolor",
        "\\AtBeginDocument",
        "\\providecommand",
        "\\setcopyright",
        "\\copyrightyear",
        "\\acmYear",
        "\\citestyle",
        "\\newcommand",
        "\\acmDOI",
        "\\newabbreviation",
        "\\global",
        "\\begin{document}",
        "\\author",
        "\\affiliation",
        "\\email",
        "\\institution",
        "\\streetaddress",
        "\\city",
        "\\country",
        "\\postcode",
        "\\ccsdesc",
        "\\received",
        "\\includegraphics",
        "\\caption",
        "\\centering",
        "\\label",
        "\\maketitle",
        "\\toprule",
        "\\multirow",
        "\\multicolumn",
        "\\cmidrule",
        "\\addlinespace",
        "\\midrule",
        "\\cellcolor",
        "\\bibliography",
        "}",
        "\\title",
        "</ccs2012>",
        "\\bottomrule",
        "<concept>",
        "<concept",
        "<ccs",
        "\\item",
        "</concept",
        "\\begin{abstract}",
        "\\end{abstract}",
        "\\endinput",
        "\\\\",
    ]
    self.latex_begin_dictionary = [
        "\\begin{figure}",
        "\\begin{figure*}",
        "\\begin{equation}",
        "\\begin{CCSXML}",
        "\\begin{teaserfigure}",
        "\\begin{table*}",
        "\\begin{table}",
        "\\begin{gather}",
        "\\begin{align}",
    ]
    self.latex_end_dictionary = [
        "\\end{figure}",
        "\\end{figure*}",
        "\\end{equation}",
        "\\end{CCSXML}",
        "\\end{teaserfigure}",
        "\\end{table*}",
        "\\end{table}",
        "\\end{gather}",
        "\\end{align}",
    ]
    self._label_lines()

get_line(line_id=0)

Definition to get a specific line by inputting a line nunber.

Returns:

  • line ( str ) –

    Requested line.

  • content_type ( str ) –

    Line's content type (e.g., latex, comment, text).

Source code in odak/tools/latex.py
def get_line(self, line_id=0):
    """
    Definition to get a specific line by inputting a line nunber.


    Returns
    ----------
    line           : str
                     Requested line.
    content_type   : str
                     Line's content type (e.g., latex, comment, text).
    """
    line = self.content[line_id]
    content_type = self.content_type[line_id]
    return line, content_type

get_line_count()

Definition to get the line count.

Returns:

  • line_count ( int ) –

    Number of lines in the loaded latex document.

Source code in odak/tools/latex.py
def get_line_count(self):
    """
    Definition to get the line count.


    Returns
    -------
    line_count     : int
                     Number of lines in the loaded latex document.
    """
    self.line_count = len(self.content)
    return self.line_count

set_latex_dictonaries(begin_dictionary, end_dictionary, syntax_dictionary)

Set document specific dictionaries so that the lines could be labelled in accordance.

Parameters:

  • begin_dictionary
                   Pythonic list containing latex syntax for begin commands (i.e. \begin{align}).
    
  • end_dictionary
                   Pythonic list containing latex syntax for end commands (i.e. \end{table}).
    
  • syntax_dictionary
                   Pythonic list containing latex syntax (i.e. \item).
    
Source code in odak/tools/latex.py
def set_latex_dictonaries(
    self, begin_dictionary, end_dictionary, syntax_dictionary
):
    """
    Set document specific dictionaries so that the lines could be labelled in accordance.


    Parameters
    ----------
    begin_dictionary     : list
                           Pythonic list containing latex syntax for begin commands (i.e. \\begin{align}).
    end_dictionary       : list
                           Pythonic list containing latex syntax for end commands (i.e. \\end{table}).
    syntax_dictionary    : list
                           Pythonic list containing latex syntax (i.e. \\item).

    """
    self.latex_begin_dictionary = begin_dictionary
    self.latex_end_dictionary = end_dictionary
    self.latex_dictionary = syntax_dictionary
    self._label_lines

blur_gaussian(field, kernel_length=[21, 21], nsigma=[3, 3])

A definition to blur a field using a Gaussian kernel.

Parameters:

  • field
            MxN field.
    
  • kernel_length (list, default: [21, 21] ) –
            Length of the Gaussian kernel along X and Y axes.
    
  • nsigma
            Sigma of the Gaussian kernel along X and Y axes.
    

Returns:

  • blurred_field ( ndarray ) –

    Blurred field.

Source code in odak/tools/matrix.py
def blur_gaussian(field, kernel_length=[21, 21], nsigma=[3, 3]):
    """
    A definition to blur a field using a Gaussian kernel.

    Parameters
    ----------
    field         : ndarray
                    MxN field.
    kernel_length : list
                    Length of the Gaussian kernel along X and Y axes.
    nsigma        : list
                    Sigma of the Gaussian kernel along X and Y axes.

    Returns
    ----------
    blurred_field : ndarray
                    Blurred field.
    """
    kernel = generate_2d_gaussian(kernel_length, nsigma)
    kernel = zero_pad(kernel, field.shape)
    blurred_field = convolve2d(field, kernel)
    blurred_field = blurred_field / np.amax(blurred_field)
    return blurred_field

convolve2d(field, kernel)

Definition to convolve a field with a kernel by multiplying in frequency space.

Parameters:

  • field
          Input field with MxN shape.
    
  • kernel
          Input kernel with MxN shape.
    

Returns:

  • new_field ( ndarray ) –

    Convolved field.

Source code in odak/tools/matrix.py
def convolve2d(field, kernel):
    """
    Definition to convolve a field with a kernel by multiplying in frequency space.

    Parameters
    ----------
    field       : ndarray
                  Input field with MxN shape.
    kernel      : ndarray
                  Input kernel with MxN shape.

    Returns
    ----------
    new_field   : ndarray
                  Convolved field.
    """
    fr = np.fft.fft2(field)
    fr2 = np.fft.fft2(np.flipud(np.fliplr(kernel)))
    m, n = fr.shape
    new_field = np.real(np.fft.ifft2(fr * fr2))
    new_field = np.roll(new_field, int(-m / 2 + 1), axis=0)
    new_field = np.roll(new_field, int(-n / 2 + 1), axis=1)
    return new_field

create_empty_list(dimensions=[1, 1])

A definition to create an empty Pythonic list.

Parameters:

  • dimensions
           Dimensions of the list to be created.
    

Returns:

  • new_list ( list ) –

    New empty list.

Source code in odak/tools/matrix.py
def create_empty_list(dimensions=[1, 1]):
    """
    A definition to create an empty Pythonic list.

    Parameters
    ----------
    dimensions   : list
                   Dimensions of the list to be created.

    Returns
    -------
    new_list     : list
                   New empty list.
    """
    new_list = 0
    for n in reversed(dimensions):
        new_list = [new_list] * n
    return new_list

crop_center(field, size=None)

Definition to crop the center of a field with 2Mx2N size. The outcome is a MxN array.

Parameters:

  • field
          Input field 2Mx2N array.
    

Returns:

  • cropped ( ndarray ) –

    Cropped version of the input field.

Source code in odak/tools/matrix.py
def crop_center(field, size=None):
    """
    Definition to crop the center of a field with 2Mx2N size. The outcome is a MxN array.

    Parameters
    ----------
    field       : ndarray
                  Input field 2Mx2N array.

    Returns
    ----------
    cropped     : ndarray
                  Cropped version of the input field.
    """
    if type(size) == type(None):
        qx = int(np.ceil(field.shape[0]) / 4)
        qy = int(np.ceil(field.shape[1]) / 4)
        cropped = np.copy(field[qx : 3 * qx, qy : 3 * qy])
    else:
        cx = int(np.ceil(field.shape[0] / 2))
        cy = int(np.ceil(field.shape[1] / 2))
        hx = int(np.ceil(size[0] / 2))
        hy = int(np.ceil(size[1] / 2))
        cropped = np.copy(field[cx - hx : cx + hx, cy - hy : cy + hy])
    return cropped

generate_2d_gaussian(kernel_length=[21, 21], nsigma=[3, 3])

Generate 2D Gaussian kernel. Inspired from https://stackoverflow.com/questions/29731726/how-to-calculate-a-gaussian-kernel-matrix-efficiently-in-numpy

Parameters:

  • kernel_length (list, default: [21, 21] ) –
            Length of the Gaussian kernel along X and Y axes.
    
  • nsigma
            Sigma of the Gaussian kernel along X and Y axes.
    

Returns:

  • kernel_2d ( ndarray ) –

    Generated Gaussian kernel.

Source code in odak/tools/matrix.py
def generate_2d_gaussian(kernel_length=[21, 21], nsigma=[3, 3]):
    """
    Generate 2D Gaussian kernel. Inspired from https://stackoverflow.com/questions/29731726/how-to-calculate-a-gaussian-kernel-matrix-efficiently-in-numpy

    Parameters
    ----------
    kernel_length : list
                    Length of the Gaussian kernel along X and Y axes.
    nsigma        : list
                    Sigma of the Gaussian kernel along X and Y axes.

    Returns
    ----------
    kernel_2d     : ndarray
                    Generated Gaussian kernel.
    """
    x = np.linspace(-nsigma[0], nsigma[0], kernel_length[0] + 1)
    y = np.linspace(-nsigma[1], nsigma[1], kernel_length[1] + 1)
    xx, yy = np.meshgrid(x, y)
    kernel_2d = np.exp(
        -0.5
        * (np.square(xx) / np.square(nsigma[0]) + np.square(yy) / np.square(nsigma[1]))
    )
    kernel_2d = kernel_2d / kernel_2d.sum()
    return kernel_2d

generate_bandlimits(size=[512, 512], levels=9)

A definition to calculate octaves used in bandlimiting frequencies in the frequency domain.

Parameters:

  • size
         Size of each mask in octaves.
    

Returns:

  • masks ( ndarray ) –

    Masks (Octaves).

Source code in odak/tools/matrix.py
def generate_bandlimits(size=[512, 512], levels=9):
    """
    A definition to calculate octaves used in bandlimiting frequencies in the frequency domain.

    Parameters
    ----------
    size       : list
                 Size of each mask in octaves.

    Returns
    ----------
    masks      : ndarray
                 Masks (Octaves).
    """
    masks = np.zeros((levels, size[0], size[1]))
    cx = int(size[0] / 2)
    cy = int(size[1] / 2)
    for i in range(0, masks.shape[0]):
        deltax = int((size[0]) / (2 ** (i + 1)))
        deltay = int((size[1]) / (2 ** (i + 1)))
        masks[i, cx - deltax : cx + deltax, cy - deltay : cy + deltay] = 1.0
        masks[
            i,
            int(cx - deltax / 2.0) : int(cx + deltax / 2.0),
            int(cy - deltay / 2.0) : int(cy + deltay / 2.0),
        ] = 0.0
    masks = np.asarray(masks)
    return masks

nufft2(field, fx, fy, size=None, sign=1, eps=10 ** -12)

A definition to take 2D Non-Uniform Fast Fourier Transform (NUFFT).

Parameters:

  • field
          Input field.
    
  • fx
          Frequencies along x axis.
    
  • fy
          Frequencies along y axis.
    
  • size
          Size.
    
  • sign
          Sign of the exponential used in NUFFT kernel.
    
  • eps
          Accuracy of NUFFT.
    

Returns:

  • result ( ndarray ) –

    Inverse NUFFT of the input field.

Source code in odak/tools/matrix.py
def nufft2(field, fx, fy, size=None, sign=1, eps=10 ** (-12)):
    """
    A definition to take 2D Non-Uniform Fast Fourier Transform (NUFFT).

    Parameters
    ----------
    field       : ndarray
                  Input field.
    fx          : ndarray
                  Frequencies along x axis.
    fy          : ndarray
                  Frequencies along y axis.
    size        : list
                  Size.
    sign        : float
                  Sign of the exponential used in NUFFT kernel.
    eps         : float
                  Accuracy of NUFFT.

    Returns
    ----------
    result      : ndarray
                  Inverse NUFFT of the input field.
    """
    try:
        import finufft
    except:
        print("odak.tools.nufft2 requires finufft to be installed: pip install finufft")
    image = np.copy(field).astype(np.complex128)
    result = finufft.nufft2d2(fx.flatten(), fy.flatten(), image, eps=eps, isign=sign)
    if type(size) == type(None):
        result = result.reshape(field.shape)
    else:
        result = result.reshape(size)
    return result

nuifft2(field, fx, fy, size=None, sign=1, eps=10 ** -12)

A definition to take 2D Adjoint Non-Uniform Fast Fourier Transform (NUFFT).

Parameters:

  • field
          Input field.
    
  • fx
          Frequencies along x axis.
    
  • fy
          Frequencies along y axis.
    
  • size
          Shape of the NUFFT calculated for an input field.
    
  • sign
          Sign of the exponential used in NUFFT kernel.
    
  • eps
          Accuracy of NUFFT.
    

Returns:

  • result ( ndarray ) –

    NUFFT of the input field.

Source code in odak/tools/matrix.py
def nuifft2(field, fx, fy, size=None, sign=1, eps=10 ** (-12)):
    """
    A definition to take 2D Adjoint Non-Uniform Fast Fourier Transform (NUFFT).

    Parameters
    ----------
    field       : ndarray
                  Input field.
    fx          : ndarray
                  Frequencies along x axis.
    fy          : ndarray
                  Frequencies along y axis.
    size        : list or ndarray
                  Shape of the NUFFT calculated for an input field.
    sign        : float
                  Sign of the exponential used in NUFFT kernel.
    eps         : float
                  Accuracy of NUFFT.

    Returns
    ----------
    result      : ndarray
                  NUFFT of the input field.
    """
    try:
        import finufft
    except:
        print(
            "odak.tools.nuifft2 requires finufft to be installed: pip install finufft"
        )
    image = np.copy(field).astype(np.complex128)
    if type(size) == type(None):
        result = finufft.nufft2d1(
            fx.flatten(),
            fy.flatten(),
            image.flatten(),
            image.shape,
            eps=eps,
            isign=sign,
        )
    else:
        result = finufft.nufft2d1(
            fx.flatten(),
            fy.flatten(),
            image.flatten(),
            (size[0], size[1]),
            eps=eps,
            isign=sign,
        )
    result = np.asarray(result)
    return result

quantize(image_field, bits=4)

Definitio to quantize a image field (0-255, 8 bit) to a certain bits level.

Parameters:

  • image_field (ndarray) –
          Input image field.
    
  • bits
          A value in between 0 to 8. Can not be zero.
    

Returns:

  • new_field ( ndarray ) –

    Quantized image field.

Source code in odak/tools/matrix.py
def quantize(image_field, bits=4):
    """
    Definitio to quantize a image field (0-255, 8 bit) to a certain bits level.

    Parameters
    ----------
    image_field : ndarray
                  Input image field.
    bits        : int
                  A value in between 0 to 8. Can not be zero.

    Returns
    ----------
    new_field   : ndarray
                  Quantized image field.
    """
    divider = 2 ** (8 - bits)
    new_field = image_field / divider
    new_field = new_field.astype(np.int64)
    return new_field

zero_pad(field, size=None, method='center')

Definition to zero pad a MxN array to 2Mx2N array.

Parameters:

  • field
                Input field MxN array.
    
  • size
                Size to be zeropadded.
    
  • method
                Zeropad either by placing the content to center or to the left.
    

Returns:

  • field_zero_padded ( ndarray ) –

    Zeropadded version of the input field.

Source code in odak/tools/matrix.py
def zero_pad(field, size=None, method="center"):
    """
    Definition to zero pad a MxN array to 2Mx2N array.

    Parameters
    ----------
    field             : ndarray
                        Input field MxN array.
    size              : list
                        Size to be zeropadded.
    method            : str
                        Zeropad either by placing the content to center or to the left.

    Returns
    ----------
    field_zero_padded : ndarray
                        Zeropadded version of the input field.
    """
    if type(size) == type(None):
        hx = int(np.ceil(field.shape[0]) / 2)
        hy = int(np.ceil(field.shape[1]) / 2)
    else:
        hx = int(np.ceil((size[0] - field.shape[0]) / 2))
        hy = int(np.ceil((size[1] - field.shape[1]) / 2))
    if method == "center":
        field_zero_padded = np.pad(field, ([hx, hx], [hy, hy]), constant_values=(0, 0))
    elif method == "left aligned":
        field_zero_padded = np.pad(
            field, ([0, 2 * hx], [0, 2 * hy]), constant_values=(0, 0)
        )
    if type(size) != type(None):
        field_zero_padded = field_zero_padded[0 : size[0], 0 : size[1]]
    return field_zero_padded

markdown

A class to work with markdown documents.

Source code in odak/tools/markdown.py
class markdown:
    """
    A class to work with markdown documents.
    """

    def __init__(self, filename):
        """
        Parameters
        ----------
        filename     : str
                       Source filename (i.e. sample.md).
        """
        self.filename = filename
        self.content = read_text_file(self.filename)
        self.content_type = []
        self.markdown_dictionary = [
            "#",
        ]
        self.markdown_begin_dictionary = [
            "```bash",
            "```python",
            "```",
        ]
        self.markdown_end_dictionary = [
            "```",
        ]
        self._label_lines()

    def set_dictonaries(self, begin_dictionary, end_dictionary, syntax_dictionary):
        """
        Set document specific dictionaries so that the lines could be labelled in accordance.


        Parameters
        ----------
        begin_dictionary     : list
                               Pythonic list containing markdown syntax for beginning of blocks (e.g., code, html).
        end_dictionary       : list
                               Pythonic list containing markdown syntax for end of blocks (e.g., code, html).
        syntax_dictionary    : list
                               Pythonic list containing markdown syntax (i.e. \\item).

        """
        self.markdown_begin_dictionary = begin_dictionary
        self.markdown_end_dictionary = end_dictionary
        self.markdown_dictionary = syntax_dictionary
        self._label_lines

    def _label_lines(self):
        """
        Internal function for labelling lines.
        """
        content_type_flag = False
        for line_id, line in enumerate(self.content):
            while len(line) > 0 and line[0] == " ":
                line = line[1::]
            self.content[line_id] = line
            if len(line) == 0:
                content_type = "empty"
            elif line[0] == "%":
                content_type = "comment"
            else:
                content_type = "text"
            for syntax in self.markdown_begin_dictionary:
                if line.find(syntax) != -1:
                    content_type_flag = True
                    content_type = "markdown"
            for syntax in self.markdown_dictionary:
                if line.find(syntax) != -1:
                    content_type = "markdown"
            if content_type_flag == True:
                content_type = "markdown"
                for syntax in self.markdown_end_dictionary:
                    if line.find(syntax) != -1:
                        content_type_flag = False
            self.content_type.append(content_type)

    def get_line_count(self):
        """
        Definition to get the line count.


        Returns
        -------
        line_count     : int
                         Number of lines in the loaded markdown document.
        """
        self.line_count = len(self.content)
        return self.line_count

    def get_line(self, line_id=0):
        """
        Definition to get a specific line by inputting a line nunber.


        Returns
        ----------
        line           : str
                         Requested line.
        content_type   : str
                         Line's content type (e.g., markdown, comment, text).
        """
        line = self.content[line_id]
        content_type = self.content_type[line_id]
        return line, content_type

__init__(filename)

Parameters:

  • filename
           Source filename (i.e. sample.md).
    
Source code in odak/tools/markdown.py
def __init__(self, filename):
    """
    Parameters
    ----------
    filename     : str
                   Source filename (i.e. sample.md).
    """
    self.filename = filename
    self.content = read_text_file(self.filename)
    self.content_type = []
    self.markdown_dictionary = [
        "#",
    ]
    self.markdown_begin_dictionary = [
        "```bash",
        "```python",
        "```",
    ]
    self.markdown_end_dictionary = [
        "```",
    ]
    self._label_lines()

get_line(line_id=0)

Definition to get a specific line by inputting a line nunber.

Returns:

  • line ( str ) –

    Requested line.

  • content_type ( str ) –

    Line's content type (e.g., markdown, comment, text).

Source code in odak/tools/markdown.py
def get_line(self, line_id=0):
    """
    Definition to get a specific line by inputting a line nunber.


    Returns
    ----------
    line           : str
                     Requested line.
    content_type   : str
                     Line's content type (e.g., markdown, comment, text).
    """
    line = self.content[line_id]
    content_type = self.content_type[line_id]
    return line, content_type

get_line_count()

Definition to get the line count.

Returns:

  • line_count ( int ) –

    Number of lines in the loaded markdown document.

Source code in odak/tools/markdown.py
def get_line_count(self):
    """
    Definition to get the line count.


    Returns
    -------
    line_count     : int
                     Number of lines in the loaded markdown document.
    """
    self.line_count = len(self.content)
    return self.line_count

set_dictonaries(begin_dictionary, end_dictionary, syntax_dictionary)

Set document specific dictionaries so that the lines could be labelled in accordance.

Parameters:

  • begin_dictionary
                   Pythonic list containing markdown syntax for beginning of blocks (e.g., code, html).
    
  • end_dictionary
                   Pythonic list containing markdown syntax for end of blocks (e.g., code, html).
    
  • syntax_dictionary
                   Pythonic list containing markdown syntax (i.e. \item).
    
Source code in odak/tools/markdown.py
def set_dictonaries(self, begin_dictionary, end_dictionary, syntax_dictionary):
    """
    Set document specific dictionaries so that the lines could be labelled in accordance.


    Parameters
    ----------
    begin_dictionary     : list
                           Pythonic list containing markdown syntax for beginning of blocks (e.g., code, html).
    end_dictionary       : list
                           Pythonic list containing markdown syntax for end of blocks (e.g., code, html).
    syntax_dictionary    : list
                           Pythonic list containing markdown syntax (i.e. \\item).

    """
    self.markdown_begin_dictionary = begin_dictionary
    self.markdown_end_dictionary = end_dictionary
    self.markdown_dictionary = syntax_dictionary
    self._label_lines

batch_of_rays(entry, exit)

Definition to generate a batch of rays with given entry point(s) and exit point(s). Note that the mapping is one to one, meaning nth item in your entry points list will exit from nth item in your exit list and generate that particular ray. Note that you can have a combination like nx3 points for entry or exit and 1 point for entry or exit. But if you have multiple points both for entry and exit, the number of points have to be same both for entry and exit.

Parameters:

  • entry
         Either a single point with size of 3 or multiple points with the size of nx3.
    
  • exit
         Either a single point with size of 3 or multiple points with the size of nx3.
    

Returns:

  • rays ( ndarray ) –

    Generated batch of rays.

Source code in odak/tools/sample.py
def batch_of_rays(entry, exit):
    """
    Definition to generate a batch of rays with given entry point(s) and exit point(s). Note that the mapping is one to one, meaning nth item in your entry points list will exit from nth item in your exit list and generate that particular ray. Note that you can have a combination like nx3 points for entry or exit and 1 point for entry or exit. But if you have multiple points both for entry and exit, the number of points have to be same both for entry and exit.

    Parameters
    ----------
    entry      : ndarray
                 Either a single point with size of 3 or multiple points with the size of nx3.
    exit       : ndarray
                 Either a single point with size of 3 or multiple points with the size of nx3.

    Returns
    ----------
    rays       : ndarray
                 Generated batch of rays.
    """
    norays = np.array([0, 0])
    if len(entry.shape) == 1:
        entry = entry.reshape((1, 3))
    if len(exit.shape) == 1:
        exit = exit.reshape((1, 3))
    norays = np.amax(np.asarray([entry.shape[0], exit.shape[0]]))
    if norays > exit.shape[0]:
        exit = np.repeat(exit, norays, axis=0)
    elif norays > entry.shape[0]:
        entry = np.repeat(entry, norays, axis=0)
    rays = []
    norays = int(norays)
    for i in range(norays):
        rays.append(create_ray_from_two_points(entry[i], exit[i]))
    rays = np.asarray(rays)
    return rays

box_volume_sample(no=[10, 10, 10], size=[100.0, 100.0, 100.0], center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate samples in a box volume.

Parameters:

  • no
          Number of samples.
    
  • size
          Physical size of the volume.
    
  • center
          Center location of the volume.
    
  • angles
          Tilt of the volume.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def box_volume_sample(
    no=[10, 10, 10],
    size=[100.0, 100.0, 100.0],
    center=[0.0, 0.0, 0.0],
    angles=[0.0, 0.0, 0.0],
):
    """
    Definition to generate samples in a box volume.

    Parameters
    ----------
    no          : list
                  Number of samples.
    size        : list
                  Physical size of the volume.
    center      : list
                  Center location of the volume.
    angles      : list
                  Tilt of the volume.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0], no[1], no[2], 3))
    x, y, z = np.mgrid[0 : no[0], 0 : no[1], 0 : no[2]]
    step = [size[0] / no[0], size[1] / no[1], size[2] / no[2]]
    samples[:, :, :, 0] = x * step[0] + step[0] / 2.0 - size[0] / 2.0
    samples[:, :, :, 1] = y * step[1] + step[1] / 2.0 - size[1] / 2.0
    samples[:, :, :, 2] = z * step[2] + step[2] / 2.0 - size[2] / 2.0
    samples = samples.reshape(
        (samples.shape[0] * samples.shape[1] * samples.shape[2], samples.shape[3])
    )
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

circular_sample(no=[10, 10], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate samples inside a circle over a surface.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of the circle.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def circular_sample(
    no=[10, 10], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate samples inside a circle over a surface.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of the circle.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0] + 1, no[1] + 1, 3))
    r_angles, r = np.mgrid[0 : no[0] + 1, 0 : no[1] + 1]
    r = r / np.amax(r) * radius
    r_angles = r_angles / np.amax(r_angles) * np.pi * 2
    samples[:, :, 0] = r * np.cos(r_angles)
    samples[:, :, 1] = r * np.sin(r_angles)
    samples = samples[1 : no[0] + 1, 1 : no[1] + 1, :]
    samples = samples.reshape((samples.shape[0] * samples.shape[1], samples.shape[2]))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

circular_uniform_random_sample(no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate sample inside a circle uniformly but randomly.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of the circle.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def circular_uniform_random_sample(
    no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate sample inside a circle uniformly but randomly.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of the circle.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.empty((0, 3))
    rs = np.sqrt(np.random.uniform(0, 1, no[0]))
    angs = np.random.uniform(0, 2 * np.pi, no[1])
    for i in rs:
        for angle in angs:
            r = radius * i
            point = np.array([float(r * np.cos(angle)), float(r * np.sin(angle)), 0])
            samples = np.vstack((samples, point))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

circular_uniform_sample(no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate sample inside a circle uniformly.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of the circle.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def circular_uniform_sample(
    no=[10, 50], radius=10.0, center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate sample inside a circle uniformly.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of the circle.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.empty((0, 3))
    for i in range(0, no[0]):
        r = i / no[0] * radius
        ang_no = no[1] * i / no[0]
        for j in range(0, int(no[1] * i / no[0])):
            angle = j / ang_no * 2 * np.pi
            point = np.array([float(r * np.cos(angle)), float(r * np.sin(angle)), 0])
            samples = np.vstack((samples, point))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

grid_sample(no=[10, 10], size=[100.0, 100.0], center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0])

Definition to generate samples over a surface.

Parameters:

  • no
          Number of samples.
    
  • size
          Physical size of the surface.
    
  • center
          Center location of the surface.
    
  • angles
          Tilt of the surface.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def grid_sample(
    no=[10, 10], size=[100.0, 100.0], center=[0.0, 0.0, 0.0], angles=[0.0, 0.0, 0.0]
):
    """
    Definition to generate samples over a surface.

    Parameters
    ----------
    no          : list
                  Number of samples.
    size        : list
                  Physical size of the surface.
    center      : list
                  Center location of the surface.
    angles      : list
                  Tilt of the surface.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0], no[1], 3))
    step = [size[0] / (no[0] - 1), size[1] / (no[1] - 1)]
    x, y = np.mgrid[0 : no[0], 0 : no[1]]
    samples[:, :, 0] = x * step[0] - size[0] / 2.0
    samples[:, :, 1] = y * step[1] - size[1] / 2.0
    samples = samples.reshape((samples.shape[0] * samples.shape[1], samples.shape[2]))
    samples = rotate_points(samples, angles=angles, offset=center)
    return samples

random_sample_point_cloud(point_cloud, no, p=None)

Definition to pull a subset of points from a point cloud with a given probability.

Parameters:

  • point_cloud
           Point cloud array.
    
  • no
           Number of samples.
    
  • p
           Probability list in the same size as no.
    

Returns:

  • subset ( ndarray ) –

    Subset of the given point cloud.

Source code in odak/tools/sample.py
def random_sample_point_cloud(point_cloud, no, p=None):
    """
    Definition to pull a subset of points from a point cloud with a given probability.

    Parameters
    ----------
    point_cloud  : ndarray
                   Point cloud array.
    no           : list
                   Number of samples.
    p            : list
                   Probability list in the same size as no.

    Returns
    ----------
    subset       : ndarray
                   Subset of the given point cloud.
    """
    choice = np.random.choice(point_cloud.shape[0], no, p)
    subset = point_cloud[choice, :]
    return subset

sphere_sample(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2])

Definition to generate a regular sample set on the surface of a sphere using polar coordinates.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of a sphere.
    
  • center
          Center of a sphere.
    
  • k
          Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def sphere_sample(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2]):
    """
    Definition to generate a regular sample set on the surface of a sphere using polar coordinates.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of a sphere.
    center      : list
                  Center of a sphere.
    k           : list
                  Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.

    Returns
    ----------
    samples     : ndarray
                  Samples generated.
    """
    samples = np.zeros((no[0], no[1], 3))
    psi, teta = np.mgrid[0 : no[0], 0 : no[1]]
    psi = k[0] * np.pi / no[0] * psi
    teta = k[1] * np.pi / no[1] * teta
    samples[:, :, 0] = center[0] + radius * np.sin(psi) * np.cos(teta)
    samples[:, :, 1] = center[0] + radius * np.sin(psi) * np.sin(teta)
    samples[:, :, 2] = center[0] + radius * np.cos(psi)
    samples = samples.reshape((no[0] * no[1], 3))
    return samples

sphere_sample_uniform(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2])

Definition to generate an uniform sample set on the surface of a sphere using polar coordinates.

Parameters:

  • no
          Number of samples.
    
  • radius
          Radius of a sphere.
    
  • center
          Center of a sphere.
    
  • k
          Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.
    

Returns:

  • samples ( ndarray ) –

    Samples generated.

Source code in odak/tools/sample.py
def sphere_sample_uniform(no=[10, 10], radius=1.0, center=[0.0, 0.0, 0.0], k=[1, 2]):
    """
    Definition to generate an uniform sample set on the surface of a sphere using polar coordinates.

    Parameters
    ----------
    no          : list
                  Number of samples.
    radius      : float
                  Radius of a sphere.
    center      : list
                  Center of a sphere.
    k           : list
                  Multipliers for gathering samples. If you set k=[1,2] it will draw samples from a perfect sphere.


    Returns
    ----------
    samples     : ndarray
                  Samples generated.

    """
    samples = np.zeros((no[0], no[1], 3))
    row = np.arange(0, no[0])
    psi, teta = np.mgrid[0 : no[0], 0 : no[1]]
    for psi_id in range(0, no[0]):
        psi[psi_id] = np.roll(row, psi_id, axis=0)
        teta[psi_id] = np.roll(row, -psi_id, axis=0)
    psi = k[0] * np.pi / no[0] * psi
    teta = k[1] * np.pi / no[1] * teta
    samples[:, :, 0] = center[0] + radius * np.sin(psi) * np.cos(teta)
    samples[:, :, 1] = center[1] + radius * np.sin(psi) * np.sin(teta)
    samples[:, :, 2] = center[2] + radius * np.cos(psi)
    samples = samples.reshape((no[0] * no[1], 3))
    return samples

closest_point_to_a_ray(point, ray)

Definition to calculate the point on a ray that is closest to given point.

Parameters:

  • point
            Given point in X,Y,Z.
    
  • ray
            Given ray.
    

Returns:

  • closest_point ( ndarray ) –

    Calculated closest point.

Source code in odak/tools/vector.py
def closest_point_to_a_ray(point, ray):
    """
    Definition to calculate the point on a ray that is closest to given point.

    Parameters
    ----------
    point         : list
                    Given point in X,Y,Z.
    ray           : ndarray
                    Given ray.

    Returns
    ---------
    closest_point : ndarray
                    Calculated closest point.
    """
    from odak.raytracing import propagate_a_ray

    if len(ray.shape) == 2:
        ray = ray.reshape((1, 2, 3))
    p0 = ray[:, 0]
    p1 = propagate_a_ray(ray, 1.0)
    if len(p1.shape) == 2:
        p1 = p1.reshape((1, 2, 3))
    p1 = p1[:, 0]
    p1 = p1.reshape(3)
    p0 = p0.reshape(3)
    point = point.reshape(3)
    closest_distance = -np.dot((p0 - point), (p1 - p0)) / np.sum((p1 - p0) ** 2)
    closest_point = propagate_a_ray(ray, closest_distance)[0]
    return closest_point

cross_product(vector1, vector2)

Definition to cross product two vectors and return the resultant vector. Used method described under: http://en.wikipedia.org/wiki/Cross_product

Parameters:

  • vector1
           A vector/ray.
    
  • vector2
           A vector/ray.
    

Returns:

  • ray ( ndarray ) –

    Array that contains starting points and cosines of a created ray.

Source code in odak/tools/vector.py
def cross_product(vector1, vector2):
    """
    Definition to cross product two vectors and return the resultant vector. Used method described under: http://en.wikipedia.org/wiki/Cross_product

    Parameters
    ----------
    vector1      : ndarray
                   A vector/ray.
    vector2      : ndarray
                   A vector/ray.

    Returns
    ----------
    ray          : ndarray
                   Array that contains starting points and cosines of a created ray.
    """
    angle = np.cross(vector1[1].T, vector2[1].T)
    angle = np.asarray(angle)
    ray = np.array([vector1[0], angle], dtype=np.float32)
    return ray

distance_between_point_clouds(points0, points1)

A definition to find distance between every point in one cloud to other points in the other point cloud.

Parameters:

  • points0
          Mx3 points.
    
  • points1
          Nx3 points.
    

Returns:

  • distances ( ndarray ) –

    MxN distances.

Source code in odak/tools/vector.py
def distance_between_point_clouds(points0, points1):
    """
    A definition to find distance between every point in one cloud to other points in the other point cloud.
    Parameters
    ----------
    points0     : ndarray
                  Mx3 points.
    points1     : ndarray
                  Nx3 points.

    Returns
    ----------
    distances   : ndarray
                  MxN distances.
    """
    c = points1.reshape((1, points1.shape[0], points1.shape[1]))
    a = np.repeat(c, points0.shape[0], axis=0)
    b = points0.reshape((points0.shape[0], 1, points0.shape[1]))
    b = np.repeat(b, a.shape[1], axis=1)
    distances = np.sqrt(np.sum((a - b) ** 2, axis=2))
    return distances

distance_between_two_points(point1, point2)

Definition to calculate distance between two given points.

Parameters:

  • point1
          First point in X,Y,Z.
    
  • point2
          Second point in X,Y,Z.
    

Returns:

  • distance ( float ) –

    Distance in between given two points.

Source code in odak/tools/vector.py
def distance_between_two_points(point1, point2):
    """
    Definition to calculate distance between two given points.

    Parameters
    ----------
    point1      : list
                  First point in X,Y,Z.
    point2      : list
                  Second point in X,Y,Z.

    Returns
    ----------
    distance    : float
                  Distance in between given two points.
    """
    point1 = np.asarray(point1)
    point2 = np.asarray(point2)
    if len(point1.shape) == 1 and len(point2.shape) == 1:
        distance = np.sqrt(np.sum((point1 - point2) ** 2))
    elif len(point1.shape) == 2 or len(point2.shape) == 2:
        distance = np.sqrt(np.sum((point1 - point2) ** 2, axis=1))
    return distance

point_to_ray_distance(point, ray_point_0, ray_point_1)

Definition to find point's closest distance to a line represented with two points.

Parameters:

  • point
          Point to be tested.
    
  • ray_point_0 (ndarray) –
          First point to represent a line.
    
  • ray_point_1 (ndarray) –
          Second point to represent a line.
    

Returns:

  • distance ( float ) –

    Calculated distance.

Source code in odak/tools/vector.py
def point_to_ray_distance(point, ray_point_0, ray_point_1):
    """
    Definition to find point's closest distance to a line represented with two points.

    Parameters
    ----------
    point       : ndarray
                  Point to be tested.
    ray_point_0 : ndarray
                  First point to represent a line.
    ray_point_1 : ndarray
                  Second point to represent a line.

    Returns
    ----------
    distance    : float
                  Calculated distance.
    """
    distance = np.sum(
        np.cross((point - ray_point_0), (point - ray_point_1)) ** 2
    ) / np.sum((ray_point_1 - ray_point_0) ** 2)
    return distance

same_side(p1, p2, a, b)

Definition to figure which side a point is on with respect to a line and a point. See http://www.blackpawn.com/texts/pointinpoly/ for more. If p1 and p2 are on the sameside, this definition returns True.

Parameters:

  • p1
          Point(s) to check.
    
  • p2
          This is the point check against.
    
  • a
          First point that forms the line.
    
  • b
          Second point that forms the line.
    
Source code in odak/tools/vector.py
def same_side(p1, p2, a, b):
    """
    Definition to figure which side a point is on with respect to a line and a point. See http://www.blackpawn.com/texts/pointinpoly/ for more. If p1 and p2 are on the sameside, this definition returns True.

    Parameters
    ----------
    p1          : list
                  Point(s) to check.
    p2          : list
                  This is the point check against.
    a           : list
                  First point that forms the line.
    b           : list
                  Second point that forms the line.
    """
    ba = np.subtract(b, a)
    p1a = np.subtract(p1, a)
    p2a = np.subtract(p2, a)
    cp1 = np.cross(ba, p1a)
    cp2 = np.cross(ba, p2a)
    test = np.dot(cp1, cp2)
    if len(p1.shape) > 1:
        return test >= 0
    if test >= 0:
        return True
    return False

rotate_point(point, angles=[0, 0, 0], mode='XYZ', origin=[0, 0, 0], offset=[0, 0, 0])

Definition to rotate a given point. Note that rotation is always with respect to 0,0,0.

Parameters:

  • point
           A point.
    
  • angles
           Rotation angles in degrees.
    
  • mode
           Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    
  • origin
           Reference point for a rotation.
    
  • offset
           Shift with the given offset.
    

Returns:

  • result ( ndarray ) –

    Result of the rotation

  • rotx ( ndarray ) –

    Rotation matrix along X axis.

  • roty ( ndarray ) –

    Rotation matrix along Y axis.

  • rotz ( ndarray ) –

    Rotation matrix along Z axis.

Source code in odak/tools/transformation.py
def rotate_point(
    point, angles=[0, 0, 0], mode="XYZ", origin=[0, 0, 0], offset=[0, 0, 0]
):
    """
    Definition to rotate a given point. Note that rotation is always with respect to 0,0,0.

    Parameters
    ----------
    point        : ndarray
                   A point.
    angles       : list
                   Rotation angles in degrees.
    mode         : str
                   Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    origin       : list
                   Reference point for a rotation.
    offset       : list
                   Shift with the given offset.

    Returns
    ----------
    result       : ndarray
                   Result of the rotation
    rotx         : ndarray
                   Rotation matrix along X axis.
    roty         : ndarray
                   Rotation matrix along Y axis.
    rotz         : ndarray
                   Rotation matrix along Z axis.
    """
    point = np.asarray(point)
    point -= np.asarray(origin)
    rotx = rotmatx(angles[0])
    roty = rotmaty(angles[1])
    rotz = rotmatz(angles[2])
    if mode == "XYZ":
        result = np.dot(rotz, np.dot(roty, np.dot(rotx, point)))
    elif mode == "XZY":
        result = np.dot(roty, np.dot(rotz, np.dot(rotx, point)))
    elif mode == "YXZ":
        result = np.dot(rotz, np.dot(rotx, np.dot(roty, point)))
    elif mode == "ZXY":
        result = np.dot(roty, np.dot(rotx, np.dot(rotz, point)))
    elif mode == "ZYX":
        result = np.dot(rotx, np.dot(roty, np.dot(rotz, point)))
    result += np.asarray(origin)
    result += np.asarray(offset)
    return result, rotx, roty, rotz

rotate_points(points, angles=[0, 0, 0], mode='XYZ', origin=[0, 0, 0], offset=[0, 0, 0])

Definition to rotate points.

Parameters:

  • points
           Points.
    
  • angles
           Rotation angles in degrees.
    
  • mode
           Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    
  • origin
           Reference point for a rotation.
    
  • offset
           Shift with the given offset.
    

Returns:

  • result ( ndarray ) –

    Result of the rotation

Source code in odak/tools/transformation.py
def rotate_points(
    points, angles=[0, 0, 0], mode="XYZ", origin=[0, 0, 0], offset=[0, 0, 0]
):
    """
    Definition to rotate points.

    Parameters
    ----------
    points       : ndarray
                   Points.
    angles       : list
                   Rotation angles in degrees.
    mode         : str
                   Rotation mode determines ordering of the rotations at each axis. There are XYZ,YXZ,ZXY and ZYX modes.
    origin       : list
                   Reference point for a rotation.
    offset       : list
                   Shift with the given offset.

    Returns
    ----------
    result       : ndarray
                   Result of the rotation
    """
    points = np.asarray(points)
    if angles[0] == 0 and angles[1] == 0 and angles[2] == 0:
        result = np.array(offset) + points
        return result
    points -= np.array(origin)
    rotx = rotmatx(angles[0])
    roty = rotmaty(angles[1])
    rotz = rotmatz(angles[2])
    if mode == "XYZ":
        result = np.dot(rotz, np.dot(roty, np.dot(rotx, points.T))).T
    elif mode == "XZY":
        result = np.dot(roty, np.dot(rotz, np.dot(rotx, points.T))).T
    elif mode == "YXZ":
        result = np.dot(rotz, np.dot(rotx, np.dot(roty, points.T))).T
    elif mode == "ZXY":
        result = np.dot(roty, np.dot(rotx, np.dot(rotz, points.T))).T
    elif mode == "ZYX":
        result = np.dot(rotx, np.dot(roty, np.dot(rotz, points.T))).T
    result += np.array(origin)
    result += np.array(offset)
    return result

rotmatx(angle)

Definition to generate a rotation matrix along X axis.

Parameters:

  • angle
           Rotation angles in degrees.
    

Returns:

  • rotx ( ndarray ) –

    Rotation matrix along X axis.

Source code in odak/tools/transformation.py
def rotmatx(angle):
    """
    Definition to generate a rotation matrix along X axis.

    Parameters
    ----------
    angle        : list
                   Rotation angles in degrees.

    Returns
    -------
    rotx         : ndarray
                   Rotation matrix along X axis.
    """
    angle = np.float64(angle)
    angle = np.radians(angle)
    rotx = np.array(
        [
            [1.0, 0.0, 0.0],
            [0.0, math.cos(angle), -math.sin(angle)],
            [0.0, math.sin(angle), math.cos(angle)],
        ],
        dtype=np.float64,
    )
    return rotx

rotmaty(angle)

Definition to generate a rotation matrix along Y axis.

Parameters:

  • angle
           Rotation angles in degrees.
    

Returns:

  • roty ( ndarray ) –

    Rotation matrix along Y axis.

Source code in odak/tools/transformation.py
def rotmaty(angle):
    """
    Definition to generate a rotation matrix along Y axis.

    Parameters
    ----------
    angle        : list
                   Rotation angles in degrees.

    Returns
    -------
    roty         : ndarray
                   Rotation matrix along Y axis.
    """
    angle = np.radians(angle)
    roty = np.array(
        [
            [math.cos(angle), 0.0, math.sin(angle)],
            [0.0, 1.0, 0.0],
            [-math.sin(angle), 0.0, math.cos(angle)],
        ],
        dtype=np.float64,
    )
    return roty

rotmatz(angle)

Definition to generate a rotation matrix along Z axis.

Parameters:

  • angle
           Rotation angles in degrees.
    

Returns:

  • rotz ( ndarray ) –

    Rotation matrix along Z axis.

Source code in odak/tools/transformation.py
def rotmatz(angle):
    """
    Definition to generate a rotation matrix along Z axis.

    Parameters
    ----------
    angle        : list
                   Rotation angles in degrees.

    Returns
    -------
    rotz         : ndarray
                   Rotation matrix along Z axis.
    """
    angle = np.radians(angle)
    rotz = np.array(
        [
            [math.cos(angle), -math.sin(angle), 0.0],
            [math.sin(angle), math.cos(angle), 0.0],
            [0.0, 0.0, 1.0],
        ],
        dtype=np.float64,
    )

    return rotz

tilt_towards(location, lookat)

Definition to tilt surface normal of a plane towards a point.

Parameters:

  • location
           Center of the plane to be tilted.
    
  • lookat
           Tilt towards this point.
    

Returns:

  • angles ( list ) –

    Rotation angles in degrees.

Source code in odak/tools/transformation.py
def tilt_towards(location, lookat):
    """
    Definition to tilt surface normal of a plane towards a point.

    Parameters
    ----------
    location     : list
                   Center of the plane to be tilted.
    lookat       : list
                   Tilt towards this point.

    Returns
    ----------
    angles       : list
                   Rotation angles in degrees.
    """
    dx = location[0] - lookat[0]
    dy = location[1] - lookat[1]
    dz = location[2] - lookat[2]
    dist = np.sqrt(dx**2 + dy**2 + dz**2)
    phi = np.arctan2(dy, dx)
    theta = np.arccos(dz / dist)
    angles = [0, np.degrees(theta).tolist(), np.degrees(phi).tolist()]
    return angles