Source code for node_frequency_image
# This file is part of Sympathy for Data.
# Copyright (c) 2024, Combine Control Systems AB
#
# SYMPATHY FOR DATA COMMERCIAL LICENSE
# You should have received a link to the License with Sympathy for Data.
import numpy as np
from scipy import fft
from sympathy.api import node
from sympathy.api.nodeconfig import Ports, Tag, Tags
from sympathy.api.exceptions import SyDataError
from sylib.imageprocessing.image import ImagePort
_transform_options = {
'fourier': 'Discrete Fourier Transform (FFT)',
'DCT-I': 'Discrete Cosine Transform (type I)',
'DCT-II': 'Discrete Cosine Transform (type II)',
'DCT-III': 'Discrete Cosine Transform (type III)',
'DST-I': 'Discrete Sine Transform (type I)',
'DST-II': 'Discrete Sine Transform (type II)',
'DST-III': 'Discrete Sine Transform (type III)',
}
_transform_functions = {
'fourier': fft.fft2,
'DCT-I': lambda x: fft.dctn(x, type=1),
'DCT-II': lambda x: fft.dctn(x, type=2),
'DCT-III': lambda x: fft.dctn(x, type=3),
'DST-I': lambda x: fft.dstn(x, type=1),
'DST-II': lambda x: fft.dstn(x, type=2),
'DST-III': lambda x: fft.dstn(x, type=3),
}
_inverse_transform_functions = {
'fourier': fft.ifft2,
'DCT-I': lambda x: fft.idctn(x, type=1),
'DCT-II': lambda x: fft.idctn(x, type=2),
'DCT-III': lambda x: fft.idctn(x, type=3),
'DST-I': lambda x: fft.idstn(x, type=1),
'DST-II': lambda x: fft.idstn(x, type=2),
'DST-III': lambda x: fft.idstn(x, type=3),
}
[docs]
class FrequencyTransformImage(node.Node):
"""
The Discrete Fourier Transform takes an image and decomposes it into its
constituent components in the frequency domain. This has many applications
in image processing.
All different transforms in this node are variants of the two-dimensional
Discrete Fourier Transform (DFT), and they all use the Fast Fourier
Transform (FFT) algorithm under the hood.
To inverse a transform you select that same transform and check the
:guilabel:`Inverse`.
Force real values on inverse
----------------------------
When doing inverse transforms you might find that the result is
complex, even when you expect it to be real. Mathematically, any hermitian
or conjugare-symmetric function should result in a real function after
inverse transform. Unfortunately because of discretization and/or the
inherent inaccuracy or floating-point arithmetics, this can not be
guarateed in practice. Instead the result will be *almost* real in these
cases.
If you check the option :guilabel:`Force real values on inverse` the node
will do element-wise absolute value of the result of the inverse transform,
ensuring that the output is real-valued.
"""
name = 'Frequency transform Image'
author = 'Venkata Reddy'
icon = 'fourier.svg'
description = (
"Apply 2D Discrete Fourier Transform to input image, "
"decomposing it into frequency components."
)
nodeid = 'com.sympathyfordata.imageanalysis.frequency_transform_image'
tags = Tags(Tag.Analysis.SignalProcessing)
related = [
'com.sympathyfordata.timeseriesanalysis.frequency_transform',
'com.sympathyfordata.timeseriesanalysis.spectral_transform',
'com.sympathyfordata.imageanalysis.convolution',
]
parameters = node.parameters()
parameters.set_string(
'operation', label='Operation', value='fourier',
description='Transform to apply.',
editor=node.editors.combo_editor(options=_transform_options))
parameters.set_boolean(
'inverse', label='Inverse transformation', value=False,
description=(
'Compute the inverse of the selected transform'))
parameters.set_boolean(
'real', label='Force real values on inverse', value=False,
description=(
'If checked, ensure that output after inverse transform is '
'real-valued by taking absolute value of each pixel value.'))
controllers = node.controller(
when=node.field('inverse', 'checked'),
action=node.field('real', 'enabled'))
inputs = Ports([ImagePort('Input image', name='input')])
outputs = Ports([ImagePort('Transformed image', name='output')])
def execute(self, node_context):
op = node_context.parameters['operation'].value
inverse = node_context.parameters['inverse'].value
image = node_context.input['input'].get_image()
out_img = node_context.output['output']
real_value = node_context.parameters['real'].value
# Image must be gray scale:
if image.shape[2] != 1:
raise SyDataError("Image must be gray-scale.")
image = image.squeeze(axis=2)
if inverse:
image = fft.ifftshift(image)
res = _inverse_transform_functions[op](image)
if real_value:
res = np.abs(res)
else:
res = _transform_functions[op](image)
res = fft.fftshift(res)
out_img.set_image(res)