<jupyter_start><jupyter_text>Fourier transforms of simple 1D functionsLet's have a look now at FTs of simple 1D functions. We will do the same thing for 2D functions later on, and we will also have a look at the numerical properties of the FT at a later time. This notebook is mostly based on Chapter 4 from Bracewell.Both `numpy` and `scipy` come with a Fourier transform module, and I chose arbitrarily to use the functions from `numpy`. Documentation abou the `numpy` fft module can be found here:- https://docs.scipy.org/doc/numpy/reference/routines.fft.htmlmodule-numpy.fft<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
%matplotlib inline<jupyter_output><empty_output><jupyter_text>The FT is given in `numpy` by `np.fft.fft` (`np.fft.fft2` for 2D functions) and the inverse FT is given by `np.fft.ifft`. The extra "F" in FFT stands for "fast Fourier transform", this is a way of doing the Fourier transform numerically, which we will have a look at later.Two more functions that we will need are `np.fft.fftshift` and `np.fft.ifftshift`. Those perform a shift to get the zero-frequency of our FT or inverse FT back into our image center. How and why will also be explored somewhat later, I just first want to show what some FTs look like generally.And lastly, when we move to frequency space with variable $s$ after we do a FT, we need a new array that holds this independent variable $s$. How that comes to be will also be explained a little later, but I will need that for the display of the FTs, so I also define a helper function for that. Numpy has a function for this, `np.fft.fftfreq`, which also needs to make use of the shifting.Because those shifts are important but take a lot of space when writing code, I will define my own 1D Fourier transforms in two functions, one for the FT and one for the inverse FT.<jupyter_code>def ft1d(func):
    ft = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(func)))
    return ft

def ift1d(func):
    ift = np.fft.fftshift(np.fft.ifft(np.fft.ifftshift(func)))
    return ift

def ft1d_freq(x):
    s = np.fft.fftshift(np.fft.fftfreq(x.size, d=x[-1]-x[-2]))
    return s
# We first need to generate the independent variable
x = np.linspace(-10, 10, 1000)
print("Shape of x: {}".format(x.shape))<jupyter_output><empty_output><jupyter_text>The rectangle functionThe box function is defined as \begin{equation*}    \Pi(x) = \begin{cases}               0 & \text{if } |x| > \frac{T}{2},\\               (\frac{A}{2} & \text{if } |x| = 0),\\               A & \text{if } |x| < \frac{T}{2}.          \end{cases}\end{equation*}where $A$ is it's amplitude and $T$ is the interval it spans. Numerical representation of the rectangle function The easy wayThe easy way to create a numerical represenation of this funciton would be to have a constant funcion of amplitude $A$ and simply set the area outside of $T$ to zero:<jupyter_code>A = 3
T = 5

func0 = A * np.ones_like(x)

leftzero = np.where(x < -T/2)
rightzero = np.where(x > T/2)
func0[leftzero] = 0
func0[rightzero] = 0

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func0)
plt.title("Rectangle function $\Pi(x)$")<jupyter_output><empty_output><jupyter_text>We can dump this in a function for later use.<jupyter_code>def rect1d(x, ampl, tint):
    """Rectangle function with amplitude ample and an interval tint from -tint/2 to tint/2."""
    
    if tint/2 >= np.max(x):
        raise("Your interval is larger than the provided x array.")
        
    func = A * np.ones_like(x)
    leftzero = np.where(x < -tint/2)
    rightzero = np.where(x > tint/2)
    func[leftzero] = 0
    func[rightzero] = 0
    
    return func
# Make sure the function works
rect_func = rect1d(x, A, T)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, rect_func)
plt.title("Rectangle function $\Pi(x)$")
plt.xlim(-T/2-1, T/2+1)<jupyter_output><empty_output><jupyter_text>The tedious wayI found that `scipy` has a periodic square function though, so I thought it would be good practice for what we learned in notebook 2 to construct the rectangle function from `scipy.signal.squaresquare()`. We will have to fiddle around with it a little bit, since this function is periodic but we only want the rectangle function part of it (non-periodic).In this function, we can define an amplitude`a_per` and period value `pt` in radians (per $2\pi$). Note how the square function extends from `-a_per` to `a_per`<jupyter_code>a_per = 2
pt = 2

func1 = a_per * signal.square(pt*x)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func1)<jupyter_output><empty_output><jupyter_text>We can see that its period in linear units is `pt` by $2\pi$$$P = pt / 2\pi$$<jupyter_code>plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func1)

# point out period pt
plt.axvline(pt/2*np.pi, linestyle="-.", linewidth="0.9", c='k')
plt.fill_betweenx([-a_per,a_per], 0, pt/2*np.pi, color="y", alpha=0.2)
plt.annotate('period P', xy=(pt/2*np.pi, 2*(a_per/5)), xytext=(7.5, 0.15), size=15,
            arrowprops=dict(facecolor='black', edgecolor='black', width=0.7, shrink=0.05),)<jupyter_output><empty_output><jupyter_text>First, we'll want to shift the function such that we have the middle box centered on zero. As we have seen in notebook 2, we can do that by introducing a phase lag. And how large does the phase lag have to be? Knowing that our period is $2\pi$, shifting it by $2\pi$ would give us exactly the same answer. If we shift it by half that, $\pi$, we would put the other edge of the box on the origin. So, we want to shift it by only half that, which is $\pi /2$, so that we get our middle box centered on the origin. If we use a minuts, we shift it to the right and if we use a plus, we shift it to the left, which is what we need.<jupyter_code>func2 = a_per * signal.square(pt*x + np.pi/2)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func2)

# point out period pt
plt.axvline(pt/2*np.pi, linestyle="-.", linewidth="0.9", c='k')
plt.fill_betweenx([-a_per,a_per], 0, pt/2*np.pi, color="y", alpha=0.2)
plt.annotate('period P', xy=(pt/2*np.pi, 2*(a_per/5)), xytext=(7.5, 0.15), size=15,
            arrowprops=dict(facecolor='black', edgecolor='black', width=0.7, shrink=0.05),)<jupyter_output><empty_output><jupyter_text>Second, we want to shift the entire function up so that we have no negative values, since we defined the rectangle function between an amplitude A and 0. Shifting it up by `a_per/2` means though that its amplitude gets twice as high, so we will define its "real" amplitude as `A = a_per / 2` and we also have to scale the square function by halt the real amplitude to make this work.<jupyter_code>A = a_per / 2
print("A = {}".format(A))
func3 = A/2 * signal.square(pt*x + np.pi/2) + A/2

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func3)

# point out period pt
plt.axvline(pt/2*np.pi, linestyle="-.", linewidth="0.9", c='k')
plt.fill_betweenx([0,A], 0, pt/2*np.pi, color="y", alpha=0.2)
plt.annotate('period P', xy=(pt/2*np.pi, 2*(A/5)), xytext=(7.5, 0.15), size=15,
            arrowprops=dict(facecolor='black', edgecolor='black', width=0.7, shrink=0.05),)<jupyter_output><empty_output><jupyter_text>Next, we'll want to chop off a) all the boxes but the middle one and b) the negative parts, so that we actually get a function between its maximum and 0 that is non-periodic.<jupyter_code>indl = np.where(x < -(np.pi/pt))    # nulling left side
func3[indl] = 0
indr = np.where(x > (np.pi/pt))     # nulling right side
func3[indr] = 0

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func3)<jupyter_output><empty_output><jupyter_text>Lastly, we want to be able to control the width $T$ of our rectangle function. If we look at the last plot in which we still showed the periodic square function and the shaded period, we can see that a full period holds four half-sizes of our rectangle, meaning $\frac{T}{2} = \frac{P}{4}$. Since $P = pt / 2\pi$, we can say:$$\frac{T}{2} = \frac{2 \pi}{4 \cdot pt}$$And since we want to be able to set our interval $T$ directly but need `pt` for the definition of the function, we will use:$$pt = \frac{2 \pi 2}{4 T} = \frac{\pi}{T}$$<jupyter_code>T = 2
pt = np.pi / T

# Define the rectangle function with A and T
func4 = A/2 * signal.square(pt*x + np.pi/2)

# Shift it up
func4 = func4 + A/2

# Extract the middle box only
indl = np.where(x < -T)    # nulling left side
func4[indl] = 0
indr = np.where(x > T)     # nulling right side
func4[indr] = 0

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, func4)
plt.xlim(-T-1, T+1)
#TODO: add ticks for -T/2 and T/2

# Check that the first positive zero of the rectangle function is indeed at T/2
sel = np.where(func4[int(len(x)/2):]<0.0001)
print("First zero: {}".format(x[int(len(x)/2):][sel][0]))
print("T/2: {}".format(T/2))<jupyter_output><empty_output><jupyter_text>Let's set our rectangle function up then<jupyter_code>A = 1
T = 1

pt = np.pi / T
rec = A/2 * signal.square(pt*x + np.pi/2)    # we use A/2 because we shift the function upwards to be between A and 0
                                           # the + np.pi/2 makes it be centered on 0

# shift it so that we only have positive value A and zero, not 
rec = rec + A/2
# We want to keep only the central box, not the periodic function
indl = np.where(x < -T)    # nulling left side
rec[indl] = 0
indr = np.where(x > T)     # nulling right side
rec[indr] = 0

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, rec)
plt.title("Rectangle function $\Pi(x)$")
#TODO: add ticks for -T/2 and T/2
plt.xlim(-1, 1)<jupyter_output><empty_output><jupyter_text>FT of the rectangle functionIt's FT can easily be found analytically (this is taken from http://www.thefouriertransform.com/transform/fourier.php):\begin{align*}    f(x) &= \Pi(x) \\    \mathscr{F}\{f(x)\} = F(s) &= \int_{-\infty}^{\infty} f(x) e^{-i 2 \pi x s} dx = \\    &= \int_{-T/2}^{T/2} A(x) e^{-i 2 \pi x s} dx = \frac{A}{-2 \pi i x} \left[e^{-i 2 \pi x s} \right]^{T/2}_{-T/2} = \\    &= \frac{A}{-2 \pi i x} \left[e^{-i \pi T s} - e^{i \pi T s} \right] = \frac{A T}{\pi s T} \left[\frac{e^{i \pi T s} - e^{-i \pi T s}}{2i} \right] = \\    &= \frac{A T}{\pi s T} sin(\pi s T) = A T \frac{sin(\pi s T)}{\pi s T} = \\    \mathscr{F}\{\Pi(x)\} &= A T \left[sinc(sT)\right]\end{align*}Which is a **sinc** function that is scaled by the amplitude and interval of $f(x)$! We can confirm this with a numerical FT:<jupyter_code># Calculate the FT
rec_ft = ft1d(rec)

# Calculate the frequency array
s = ft1d_freq(x)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("s")
plt.ylabel("F(s)")

plt.plot(s, rec_ft)
plt.title("$\mathscr{F}\{\Pi(x)\}$")

#TODO: frequency ticks on x-axis (1/T and so on)
#TODO: explain amplitude scaling<jupyter_output><empty_output><jupyter_text>Aaaaaand we did our first Fourier transform! If you remember from notebook number 1, we said that $f(x)$ and $F(s)$ form a Fourier transform pair and this means that we can Fourier transform back and forth between the two.We will see that it matters if we take the FT or the inverse FT.<jupyter_code># Take the iFT of the FT
ft_back = ft1d(rec_ft)

# Take the FT of the FT
ift_back = ift1d(rec_ft)

# Plot
plt.figure(figsize=(15, 8))

plt.subplot(1, 2, 1)
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(s, ft_back)
plt.title("FT of the FT")

plt.subplot(1, 2, 2)
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(s, ift_back)
plt.title("iFT of the FT")<jupyter_output><empty_output><jupyter_text>As you can see, if we take the FT of the FT, then the overall shape of $f(x)$ gets recovered, but its scaling (normalization) is way, way off. By taking the inverse Fourier transform instead, this is taken care of properly. Note that different FT implementations (`numpy` vs. `scipy` vs. others) might deal with the normalization differently, so you have to be informed as to how they normalize their FT and iFT, respectively.)Note how the normalization of the FT is very important in general; we will cover this, like many other things, at a later point. One thing I do want to stress though is that if we have a setup in which we take a FT and then an iFT and only then we deal with our data, we don't really care about *how* the normalization happens, as going both ways will come back to the initial function. The only thing that is important here is that you use the FT and iFT from the same framework (here: same module, e.g. `numpy.fft`) so that this works out properly. If you work directly with the FT though (or the iFT), without having gone both way with the transformation, it is important that you take care of the normalization properly.The documentation on numpy's fft module explains that the direct ransform is unscaled and the inverse transform is scaled by $1/n$, where n is the number of discrete points in the function. There is a keyword though that can change that to a normalization of $1/\sqrt{n}$ both ways.(https://docs.scipy.org/doc/numpy/reference/routines.fft.htmlnormalization)And for the sake of completeness, here are $f(x)$ and $\mathscr{F}^{-1}\{F(s)\}$ next to each other:<jupyter_code>plt.figure(figsize=(15, 5))

plt.subplot(1, 2, 1)
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, rec)
plt.title("Rectangle function $\Pi(x)$")
#TODO: add ticks for -T/2 and T/2
plt.xlim(-1, 1)

plt.subplot(1, 2, 2)
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, ift_back)
plt.title("iFT of the FT of $\Pi(x)$")
#TODO: add ticks for -T/2 and T/2
plt.xlim(-1, 1)<jupyter_output><empty_output><jupyter_text>The triangle functionThe triangle funciton is by definition\begin{equation*}    \Lambda(x) = \begin{cases}               0 & \text{if } |x| > \frac{T}{2},\\               A-|x| & \text{if } |x| < \frac{T}{2}.          \end{cases}\end{equation*}  Numerical representationWe'll have to go through a similar ordeal like for the rectangle function, but this time with `scipy`'s `signal.sawtooth()` function.<jupyter_code># Define amplitude and interval
A = 1
T = 1

# Define function
pt = np.pi / T
tri = A/2 * signal.sawtooth(pt*x + np.pi, width=0.5) + A/2    # width=0.5 makes it simmetrycal

# Discard all but the central triangle
indl = np.where(x < -T)    # nulling left side
tri[indl] = 0
indr = np.where(x > T)     # nulling right side
tri[indr] = 0

# Plot
plt.figure(figsize=(15, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, tri)
plt.title("$\Lambda(x)$")
plt.xlim(-2, 2)
# Make a function of it for easier use later
def triangle(x, ampl, tint):
    """Create the triangle function with amplitude ampl on interval tint from -tint/2 to tint/2."""
    pt = np.pi / tint
    tri = ampl/2 * signal.sawtooth(pt*x + np.pi, width=0.5) + ampl/2

    # Discard all but the central triangle
    indl = np.where(x < -tint)    # nulling left side
    tri[indl] = 0
    indr = np.where(x > tint)     # nulling right side
    tri[indr] = 0
    
    return tri
# Test the function
tri_func = triangle(x, 2.3, 2.7)

plt.plot(x, tri_func)<jupyter_output><empty_output><jupyter_text>FT of the trianlge functionThe analytical FT of the triangle function can be found here:http://www.thefouriertransform.com/pairs/triangle.phpThe result is a **squared sinc** function, which is also what we get when we perform a numerical FT:<jupyter_code># Calculate the FT
tri_ft = ft1d(tri)

# Calculate the frequency array
s = ft1d_freq(x)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("s")
plt.ylabel("F(s)")

plt.plot(s, tri_ft)
plt.title("$\mathscr{F}\{\Lambda(x)\}$")
plt.xlim(-4, 4)<jupyter_output><empty_output><jupyter_text>The GaussianWe define a Gaussian function:<jupyter_code>def gaussian(x, ampl, c):
    #func = ampl * np.exp(-np.square(x) / (2*np.square(c)))
    func = ampl * np.exp(-np.pi * np.square(x) / (2 * c/2))
    return func
A = 1
T = 1
gauss = gaussian(x, A, T)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, gauss)
plt.title("gauss(x)")
plt.xlim(-3, 3)

# Display lines for FWHM and T
plt.axhline(A/2, ls="-.", c="k", linewidth="0.5")
plt.axvline(-T/2, ls="-.", c="k", linewidth="0.5")
plt.axvline(T/2, ls="-.", c="k", linewidth="0.5")
#TODO: add ticks for -T/2 and T/2<jupyter_output><empty_output><jupyter_text>FT of a Gaussian<jupyter_code># Calculate the FT
gauss_ft = ft1d(gauss)

# Calculate the frequency array
s = ft1d_freq(x)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("s")
plt.ylabel("F(s)")

plt.plot(s, gauss_ft)
plt.title("$\mathscr{F}\{gauss(x)\}$")
plt.xlim(-3, 3)<jupyter_output><empty_output><jupyter_text>We can see that the FT of a Gaussian is itself a Gaussian!  The sine and cosine functionLet's take our sine and cosine definitions from notebook 2:$$c_1(x) = A cos(2\pi \nu x - \phi)$$$$s_1(x) = A sin(2\pi \nu x - \phi)$$<jupyter_code>A = 1
phi = 0
nu = 1 / (2*np.pi)
P = 1/nu

c1 = A * np.cos(2*np.pi * nu * x - phi) 
s1 = A * np.sin(2*np.pi * nu * x - phi) 

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, c1, label="cosine")
plt.plot(x, s1, label="sine")
plt.legend()<jupyter_output><empty_output><jupyter_text>What do their Fourier transforms look like?<jupyter_code># Calculate the FT
c1_ft = ft1d(c1)
s1_ft = ft1d(s1)

# Calculate the frequency array
s = ft1d_freq(x)

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("s")
plt.ylabel("F(s)")

plt.plot(s, c1_ft)
plt.plot(s, s1_ft)
plt.xlim(-1, 1)<jupyter_output><empty_output><jupyter_text>Well..., that looks kinda wonky, doesn't it? Something is off here.So wait, in the very first notebook and in the external material, we mentioned that in general, the functions we work with will be complex functions and hence the FTs we get will also be complex functions. So how come we evidently *completely* ignored that so far, since there is no way of plotting complex numbers and functions in 1D space?The answer is that so far, we simply dealt with functions who have FTs with negligible imaginary parts. And since `plt.plot()` **defaults to plotting the real part** of a complex number, and the imaginary parts were negligible, we didn't really have any problem - except that we didn't know that we were not, in fact, looking at all the information available to us.Since this is touching upon a new, very important topic, lets move on to notebook 4 with this. -----------Just another function definition, without an FT, not really important for this notebook but also useful to have: The Heaviside step functionis defined as:\begin{equation*}    H(x) = \begin{cases}               A & \text{if } x > 0,\\               0 & \text{if } x < 0.          \end{cases}\end{equation*} Numerical representation of the step functionFortunately, this one is really easy to do:<jupyter_code>A = 3

heavy = np.zeros_like(x)
heavy[np.where(x > 0)] = A

plt.figure(figsize=(10, 5))
plt.axhline(0, color='grey', linewidth='0.5')
plt.axvline(0, color='grey', linewidth='0.5')
plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, heavy)
plt.title("$H(x)$")
# Lets make a function of that too
def heaviside(x, ampl):
    """Create the Heavyside unit step function with amplitude ampl."""
    heavy = np.zeros_like(x)
    heavy[np.where(x > 0)] = A
    
    return heavy

# Test the function
heavy_func = heaviside(x, A)

plt.plot(x, heavy_func)<jupyter_output><empty_output><jupyter_text>The FT of the Heaviside step functionAnalytical FT:http://www.thefouriertransform.com/pairs/step.php --------------Two more functions that would be neat to do here (with their FTs): The exponentialhttp://www.thefouriertransform.com/pairs/complexexponential.php<jupyter_code>a = 0.1
expo = np.exp(a*x)
expo_ft = ft1d(expo)

plt.plot(x, expo_ft)
plt.xlim(-1, 1)

#TODO: finish exponential<jupyter_output><empty_output><jupyter_text>The Dirac delta functionhttp://www.thefouriertransform.com/pairs/impulse.php<jupyter_code>#TODO: Dirac delta function
# For later:
#TODO: explain sampling
#TODO: explain DC component<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.15036182101309883, 'fraction_numerical': 0.023495465787304205, 'mean_word_length': 4.281809385582971, 'pattern_counts': {'":': 0, '<': 104, '<?xml version=': 0, '>': 106, 'https://': 2, 'lorem ipsum': 0, 'www.': 5, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '97891', 'license': 'MIT', 'n_tokens_mistral': 8393, 'n_tokens_neox': 7891, 'n_words': 2605, 'path': '03_1D Fourier transforms.ipynb', 'repo_name': 'ivalaginja/FouFourier', 'size': '21834'}