diff --git a/src/diffpy/fourigui/tests/__init__.py b/src/__init__.py similarity index 100% rename from src/diffpy/fourigui/tests/__init__.py rename to src/__init__.py diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 586be9d..4d4bf70 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -21,4 +21,3 @@ __path__ = extend_path(__path__, __name__) # End of file - diff --git a/src/diffpy/fourigui/fourigui.py b/src/diffpy/fourigui/fourigui.py new file mode 100755 index 0000000..7035231 --- /dev/null +++ b/src/diffpy/fourigui/fourigui.py @@ -0,0 +1,571 @@ +import time +import tkinter as tk +from tkinter.ttk import Button + +import h5py +import matplotlib +import numpy as np +from matplotlib import pyplot as plt +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk + +matplotlib.use("tk.TkAgg") + +WIDTH = 920 +HEIGHT = 630 +XPOS = 300 +YPOS = 100 + + +class Gui(tk.Frame): + def __init__(self): + super().__init__() + self.initUI() + + def initUI(self): + + self.loaded = False # denotes whether a dataset is loaded + self.transformed = False # denotes whether dataset is Fourier transformed + self.cutted = False # denotes whether cutoff frequencies are applied to dataset + self.transcutted = False # denotes whether cutoff frequencies are applied and Fourier transformed + + self.master.title("FouriGUI") + self.pack(fill=tk.BOTH, expand=True) + + print("\nNew Session started ...") + print("Enjoy exploring the beautiful reconstructions in real and in reciprocal space!") + + # 4 frames: + # frame 00: all buttons + # frame 01: plot area + # frame 10: exit button + # frame 11: not used + + # 00 # + # frame 00, upper left + + frame00 = tk.Frame(self) + frame00.place(x=5, y=0) + + filelabel = tk.Label(frame00, text="filename: ") + filelabel.grid(row=0, column=0) + + # row 0: load file area + self.filename_entry = tk.Entry(frame00) + self.filename_entry.grid(row=0, column=1, columnspan=3) + self.filename_entry.insert(0, "/path/data.h5") + + loadbutton = Button(frame00, text="load", command=lambda: self.load_cube()) + loadbutton.grid(row=0, column=4) + + # row 1: change axis area + axislabel = tk.Label(frame00, text="axis: ") + axislabel.grid(row=1, column=0, pady=7, sticky=tk.W) + + self.axis = tk.IntVar() + + rb0 = tk.Radiobutton( + frame00, + text="0", + variable=self.axis, + value=0, + command=lambda: self.plot_plane(), + ) + rb0.grid(row=1, column=1) + rb1 = tk.Radiobutton( + frame00, + text="1", + variable=self.axis, + value=1, + command=lambda: self.plot_plane(), + ) + rb1.grid(row=1, column=2) + rb2 = tk.Radiobutton( + frame00, + text="2", + variable=self.axis, + value=2, + command=lambda: self.plot_plane(), + ) + rb2.grid(row=1, column=3) + + # row 2-4: intensity specs + intlabel = tk.Label(frame00, text="intensity:") + intlabel.grid(row=2, column=0, pady=1, sticky=tk.W) + maxintlabel = tk.Label(frame00, text="max:") + maxintlabel.grid(row=3, column=0, pady=1, sticky=tk.E) + minintlabel = tk.Label(frame00, text="min:") + minintlabel.grid(row=4, column=0, pady=1, sticky=tk.E) + sumintlabel = tk.Label(frame00, text="sum:") + sumintlabel.grid(row=5, column=0, pady=1, sticky=tk.E) + nanratiolabel = tk.Label(frame00, text="nan ratio:") + nanratiolabel.grid(row=6, column=0, pady=1, sticky=tk.E) + globallabel = tk.Label(frame00, text="global", width=7) + globallabel.grid(row=2, column=1) + self.globalmax = tk.Label(frame00, text="") + self.globalmax.grid(row=3, column=1) + self.globalmin = tk.Label(frame00, text="") + self.globalmin.grid(row=4, column=1) + self.globalsum = tk.Label(frame00, text="") + self.globalsum.grid(row=5, column=1) + self.globalnanratio = tk.Label(frame00, text="") + self.globalnanratio.grid(row=6, column=1) + inplanelabel = tk.Label(frame00, text="in plane", width=7) + inplanelabel.grid(row=2, column=2) + self.localmax = tk.Label(frame00, text="") + self.localmax.grid(row=3, column=2) + self.localmin = tk.Label(frame00, text="") + self.localmin.grid(row=4, column=2) + self.localsum = tk.Label(frame00, text="") + self.localsum.grid(row=5, column=2) + self.localnanratio = tk.Label(frame00, text="") + self.localnanratio.grid(row=6, column=2) + colorbarlabel = tk.Label(frame00, text="colorbar") + colorbarlabel.grid(row=2, column=3) + self.colorbarmax = tk.Entry(frame00, width=7) + self.colorbarmax.grid(row=3, column=3) + self.colorbarmin = tk.Entry(frame00, width=7) + self.colorbarmin.grid(row=4, column=3) + set_range = Button(frame00, text="set range", command=lambda: self.colorrange_upd()) + set_range.grid(row=2, column=4) + toglobalmax = Button( + frame00, + text="global max", + command=lambda: self.multiple_funcs( + self.colorbarmax.delete(0, len(self.colorbarmax.get())), + self.colorbarmax.insert(0, self.globalmax["text"]), + ), + ) + toglobalmax.grid(row=3, column=4) + toglobalmin = Button( + frame00, + text="global min", + command=lambda: self.multiple_funcs( + self.colorbarmin.delete(0, len(self.colorbarmin.get())), + self.colorbarmin.insert(0, self.globalmin["text"]), + ), + ) + toglobalmin.grid(row=4, column=4) + + # row 7-8: animation - automatic slicing through the planes + anilabel = tk.Label(frame00, text="animation speed [ms]") + anilabel.grid(row=7, column=3, columnspan=2, sticky=tk.W) + self.anientry = tk.Entry(frame00, width=7) + self.anientry.grid(row=8, column=3) + anibutton = Button(frame00, text="animation", command=lambda: self.animation()) + anibutton.grid(row=8, column=4) + + # row 10-12 Fourier transformation + seperator = tk.Label( + frame00, text=" " + ) # __________________________________________________________________") + seperator.grid(row=9, column=0, columnspan=5) + cutofflabel = tk.Label(frame00, text="cutoff frequency") + cutofflabel.grid(row=10, column=2, columnspan=2) + qminlabel = tk.Label(frame00, text="qmin [px]:") + qminlabel.grid(row=11, column=2, sticky=tk.E) + qmaxlabel = tk.Label(frame00, text="qmax [px]:") + qmaxlabel.grid(row=12, column=2, sticky=tk.E) + self.qminentry = tk.Entry(frame00, width=7) + self.qminentry.grid(row=11, column=3) + self.qmaxentry = tk.Entry(frame00, width=7) + self.qmaxentry.grid(row=12, column=3) + self.cutoff = tk.IntVar() + newcutoffbutton = Button(frame00, text="new cutoff", command=lambda: self.newcutoff()) + newcutoffbutton.grid(row=10, column=4) + cutoffon = tk.Radiobutton( + frame00, + text="on", + variable=self.cutoff, + value=1, + command=lambda: self.applycutoff(), + ) + cutoffon.grid(row=11, column=4, sticky=tk.W) + cutoffoff = tk.Radiobutton( + frame00, + text="off", + variable=self.cutoff, + value=0, + command=lambda: self.redocutuff(), + ) + cutoffoff.grid(row=12, column=4, sticky=tk.W) + + spacelabel = tk.Label(frame00, text="Space Selection") + spacelabel.grid(row=10, column=0, columnspan=2, sticky=tk.W) + self.space = tk.IntVar() + reciprocal = tk.Radiobutton( + frame00, + text="reciprocal space", + variable=self.space, + value=0, + command=lambda: self.ifft(), + pady=5, + ) + reciprocal.grid(row=11, column=0, columnspan=2, sticky=tk.W) + fft = tk.Radiobutton( + frame00, + text="real space", + variable=self.space, + value=1, + command=lambda: self.fft(), + ) + fft.grid(row=12, column=0, columnspan=2, sticky=tk.W) + + # 01 # + # frame 01, upper right + self.frame01 = tk.Frame(self, bg="#cccccc") + self.frame01.place(x=400, y=0) # , height=HEIGHT//2, width=WIDTH//2) + + self.plane_num = tk.IntVar() + + self.slider = tk.Scale( + self.frame01, + variable=self.plane_num, + from_=0, + to=500, + label="slider", + orient=tk.HORIZONTAL, + length=WIDTH // 2, # resolution=-1, + command=lambda x: self.multiple_funcs(self.plot_plane(), self.intensity_upd_local()), + ) + # command=lambda p: self.plot_plane()) + self.slider.grid(row=0, column=0, padx=10, pady=10, sticky=tk.N + tk.E + tk.S + tk.W) + + self.frame01_plotcell = tk.Frame(self.frame01) + self.frame01_plotcell.grid(row=1, column=0, padx=10, pady=10, sticky=tk.N + tk.E + tk.S + tk.W) + + self.frame01_toolbar = tk.Frame(self.frame01) + self.frame01_toolbar.grid(row=2, column=0) + + # 10 # + # frame 10, lower left + frame10 = tk.Frame(self) + frame10.place(x=5, y=HEIGHT - 30) # , height=HEIGHT//2, width=WIDTH//2) + quit = Button( + frame10, + text="exit", + command=lambda: self.multiple_funcs(print("Session ended...\n", self.quit())), + ) + quit.pack(side=tk.TOP) + + # 11 # + # frame 00, lower right + # no functionality + frame11 = tk.Frame(self) + frame11.place(x=WIDTH // 2, y=HEIGHT // 2) # , height=HEIGHT//2, width=WIDTH//2) + + def load_cube(self): + """ + loads 3D array in h5py file format from the filename input panel + 3D array is expected to be a reconstructed reciprocal scattering volume + when executed, one slide perpendicular to the selected axis will be plotted in the plot panel + """ + + filename = self.filename_entry.get() + f = h5py.File(filename, "r") + try: + if "data" in f.keys(): + self.cube = np.array(f["data"]) + elif "rebinned_data" in f.keys(): + self.cube = np.array(f["rebinned_data"]) + except Exception: + raise KeyError( + "- No data found in " + + filename + + " :( ..." + + "\nchange to alternative keys: " + + str(list(f.keys())) + ) + print("- file loaded: {}".format(filename)) + + self.slider.destroy() + self.slider = tk.Scale( + self.frame01, + variable=self.plane_num, + from_=0, + to=len(self.cube) - 1, + label="slider", + orient=tk.HORIZONTAL, + length=WIDTH // 2, # resolution=-1, + command=lambda x: self.multiple_funcs(self.plot_plane(), self.intensity_upd_local()), + ) + self.slider.grid(row=0, column=0, padx=10, pady=10, sticky=tk.N + tk.E + tk.S + tk.W) + + if not self.loaded: + + fig, ax = plt.subplots(figsize=(4.95, 4.95)) + fig = plt.gcf() + DPI = fig.get_dpi() + fig.set_size_inches(500 / float(DPI), 500 / float(DPI)) + + self.plane_num.set(np.shape(self.cube)[0] // 2) + + if self.axis.get() == 0: + self.im = plt.imshow(self.cube[self.plane_num.get(), :, :]) + elif self.axis.get() == 1: + self.im = plt.imshow(self.cube[:, self.plane_num.get(), :]) + elif self.axis.get() == 2: + self.im = plt.imshow(self.cube[:, :, self.plane_num.get()]) + else: + raise ValueError("axis must be 0,1,2") + plt.colorbar(shrink=0.81) + ax.set_xlabel("pixel") + ax.set_ylabel("pixel") + self.canvas = FigureCanvasTkAgg(fig, master=self.frame01_plotcell) + self.toolbar = NavigationToolbar2Tk(self.canvas, self.frame01_toolbar) + self.toolbar.pack(side=tk.LEFT) + # self.toolbar.children['!button6'].pack_forget() + # self.toolbar.children['!button7'].pack_forget() + self.toolbar.update() + self.canvas.draw() + self.canvas.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH, expand=1) + self.loaded = True + + else: + self.plot_plane() + self.transformed = False + self.transcutted = False + self.cutted = False + self.cutoff.set(0) + self.space.set(0) + + self.intensity_upd_global() + + def plot_plane(self): + """update plotted plane perpendicular to the selected axis""" + if self.axis.get() == 0: + self.im.set_data(self.cube[self.plane_num.get(), :, :]) + elif self.axis.get() == 1: + self.im.set_data(self.cube[:, self.plane_num.get(), :]) + elif self.axis.get() == 2: + self.im.set_data(self.cube[:, :, self.plane_num.get()]) + else: + raise ValueError("axis must be 0,1,2") + self.canvas.draw() + + def colorrange_upd(self): + """change color range in plot""" + try: + if self.colorbarmin.get() and self.colorbarmax.get(): + vmin = float(self.colorbarmin.get()) + vmax = float(self.colorbarmax.get()) + elif self.colorbarmin.get(): + vmin = float(self.colorbarmin.get()) + vmax = self.globalmax["text"] + elif self.colorbarmax.get(): + vmin = self.globalmin["text"] + vmax = float(self.colorbarmax.get()) + else: + vmin = self.globalmin["text"] + vmax = self.globalmax["text"] + except ValueError: + print("Oops... colorbar range must be a number or empty string.") + self.im.set_clim(vmin, vmax) + self.plot_plane() + + def intensity_upd_local(self): + """show local intensity minimum, maximum and sum of current plotted plane""" + if self.axis.get() == 0: + plane = self.cube[self.plane_num.get(), :, :] + elif self.axis.get() == 1: + plane = self.cube[:, self.plane_num.get(), :] + elif self.axis.get() == 2: + plane = self.cube[:, :, self.plane_num.get()] + nan_ratio = np.count_nonzero(np.isnan(plane)) / plane.size + self.localmax["text"] = "{}".format(np.format_float_scientific(np.nanmax(plane), 1)) + self.localmin["text"] = "{}".format(np.format_float_scientific(np.nanmin(plane), 1)) + self.localsum["text"] = "{}".format(np.format_float_scientific(np.nansum(plane), 1)) + self.localnanratio["text"] = "{}".format(round(nan_ratio, 2)) + + def intensity_upd_global(self): + """show global intensity minimum, maximum and sum of 3D array""" + self.intensity_upd_local() + nan_ratio = np.count_nonzero(np.isnan(self.cube)) / self.cube.size + self.globalmax["text"] = "{}".format(np.format_float_scientific(np.nanmax(self.cube), 1)) + self.globalmin["text"] = "{}".format(np.format_float_scientific(np.nanmin(self.cube), 1)) + self.globalsum["text"] = "{}".format(np.format_float_scientific(np.nansum(self.cube), 1)) + self.globalnanratio["text"] = "{}".format(round(nan_ratio, 2)) + + def fft(self): + """ + Fourier transform 3D array from reciprocal to real space + the origin of reciprocal and real space is expected to be the central voxel + """ + + def perform_fft(fftholder): + time0 = time.time() + fftholder = np.nan_to_num(fftholder) + size = list(fftholder.shape) + fftholder = np.fft.ifftshift(fftholder) + fftholder = np.fft.fftn(fftholder, s=size, norm="ortho") + fftholder = np.fft.fftshift(fftholder) + fftholder = fftholder.real + fftdur = time.time() - time0 + print("- FFT performed in {} sec.".format(round(fftdur, 4))) + return fftholder + + if not self.transformed and not self.transcutted: # no fft at all yet + if not self.cutoff.get(): + self.cube_reci = self.cube + self.cube = perform_fft(self.cube) + self.cube_real = self.cube + self.transformed = True + else: + self.cube_recicut = self.cube + self.cube = perform_fft(self.cube) + self.cube_realcut = self.cube + self.transcutted = True + + elif not self.transformed and self.transcutted: + if not self.cutoff.get(): + self.cube = perform_fft(self.cube_reci) + self.cube_real = self.cube + self.transformed = True + else: + self.cube = self.cube_realcut + + elif self.transformed and not self.transcutted: + if not self.cutoff.get(): + self.cube_reci = self.cube + self.cube = self.cube_real + else: + self.cube = perform_fft(self.cube_recicut) + # self.cube = self.cube_realcut + self.transcutted = True + + else: + if not self.cutoff.get(): + self.cube = self.cube_real + else: + self.cube = self.cube_realcut + + print("- Switching to real space") + + self.plot_plane() + self.intensity_upd_global() + + def ifft(self): + """ + Inverse Fourier transform 3D array from real to reciprocal space + the origin of real and reciprocal space is expected to be the central voxel + """ + if not self.cutoff.get(): + self.cube_real = self.cube + self.cube = self.cube_reci + else: + self.cube_realcut = self.cube + self.cube = self.cube_recicut + + print("- Switching to reciprocal space") + + self.plot_plane() + self.intensity_upd_global() + + def applycutoff(self): + """ + reassign all voxels with distance smaller than qmin and greater than qmax + from the central voxel to 0.0 + qmin, qmax is loaded from the qmin, qmax input panel + currently opperates in units of pixels + """ + if not self.cutted: + + time0 = time.time() + X, Y, Z = self.cube.shape + sphere = np.ones((X, Y, Z)) + qmin = float(self.qminentry.get()) + qmax = float(self.qmaxentry.get()) + # convert qmin to pixels + # convert qmax to pixels + r2_inner = qmin**2 + r2_outer = qmax**2 + XS, YS, ZS = np.meshgrid(np.arange(X), np.arange(Y), np.arange(Z)) + R2 = (XS - X // 2) ** 2 + (YS - Y // 2) ** 2 + (ZS - Z // 2) ** 2 + mask = (R2 <= r2_inner) | (R2 >= r2_outer) + sphere[mask] = np.nan + cutdur = time.time() - time0 + + if self.space.get(): + self.cube_real = self.cube + self.cube = self.cube_reci * sphere + self.cube_recicut = self.cube + print("- Cutoff below {} and beyond {} in {} sec.".format(qmin, qmax, round(cutdur, 4))) + self.fft() + else: + self.cube_reci = self.cube + self.cube = self.cube * sphere + self.cube_recicut = self.cube + self.plot_plane() + self.intensity_upd_global() + print("- Cutoff below {} and beyond {} in {} sec.".format(qmin, qmax, round(cutdur, 4))) + + self.cutted = True + + else: + if self.space.get(): # in real space + self.cube = self.cube_realcut + else: + self.cube = self.cube_recicut + self.plot_plane() + self.intensity_upd_global() + + def redocutuff(self): + if self.space.get(): # in real space + self.cube_realcut = self.cube + if not self.transformed: + self.fft() + self.cube = self.cube_real + else: + self.cube_recicut = self.cube + self.cube = self.cube_reci + self.plot_plane() + self.intensity_upd_global() + + def newcutoff(self): + if self.cutoff.get(): + if self.space.get() and self.transformed: + self.cube = self.cube_real + else: + self.cube = self.cube_reci + self.cutted = False + self.transcutted = False + self.applycutoff() + + def plot_next_plane(self): + n = self.plane_num.get() + if n == len(self.cube[self.axis.get()]) - 1: + n = 0 + else: + n += 1 + self.plane_num.set(n) + self.plot_plane() + + def animation(self): + """ + slices through the 3D array along the selcted axis + """ + try: + if not self.anientry.get(): + anispeed = 1 + else: + anispeed = self.anientry.get() + except ValueError: + print("Oops... animation speed must be an integer > 0 or empty string.") + n = self.plane_num.get() - 1 + while n is not self.plane_num.get(): + self.slider.after(anispeed, self.plot_next_plane()) + self.plot_next_plane() + + def multiple_funcs(*funcs): + for func in funcs: + func + + +def main(): + root = tk.Tk() + root.geometry("{}x{}+{}+{}".format(WIDTH, HEIGHT, XPOS, YPOS)) + Gui() + root.mainloop() + + +if __name__ == "__main__": + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/diffpy/fourigui/tests/conftest.py b/tests/conftest.py similarity index 100% rename from src/diffpy/fourigui/tests/conftest.py rename to tests/conftest.py diff --git a/src/diffpy/fourigui/tests/debug.py b/tests/debug.py similarity index 100% rename from src/diffpy/fourigui/tests/debug.py rename to tests/debug.py diff --git a/tests/integration_test.py b/tests/integration_test.py new file mode 100644 index 0000000..043e9ca --- /dev/null +++ b/tests/integration_test.py @@ -0,0 +1,130 @@ +import unittest + +import h5py +import numpy as np + +from ..fourigui.fourigui import Gui + + +class TestGui(unittest.TestCase): + def setUp(self): + # set up gui + self.test_gui = Gui() + + # set up test data + self.test_sofq = h5py.File("tests/testdata/sofq.h5")["data"] + self.test_sofq_cut_10to40px = h5py.File("tests/testdata/sofq_cut_10to40px.h5")["data"] + self.test_sofq_cut_15to35px = h5py.File("tests/testdata/sofq_cut_15to35px.h5")["data"] + self.test_gofr = h5py.File("tests/testdata/gofr.h5")["data"] + self.test_gofr_cut_10to40px = h5py.File("tests/testdata/gofr_from_sofq_cut_10to40px.h5")["data"] + self.test_gofr_cut_15to35px = h5py.File("tests/testdata/gofr_from_sofq_cut_15to35px.h5")["data"] + + def test_load_cube_testdataset1(self): + # given + self.test_gui.filename_entry.delete(0, "end") + self.test_gui.filename_entry.insert(0, "tests/testdata/sofq.h5") + + # when + self.test_gui.load_cube() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(result, self.test_sofq)) + + def test_load_cube_testdataset2(self): + # given + self.test_gui.filename_entry.delete(0, "end") + self.test_gui.filename_entry.insert(0, "tests/testdata/sofq_cut_10to40px.h5") + + # when + self.test_gui.load_cube() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(np.nan_to_num(result), np.nan_to_num(self.test_sofq_cut_10to40px))) + + def test_load_cube_testdataset3(self): + # given + self.test_gui.filename_entry.delete(0, "end") + self.test_gui.filename_entry.insert(0, "tests/testdata/sofq_cut_15to35px.h5") + + # when + self.test_gui.load_cube() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(np.nan_to_num(result), np.nan_to_num(self.test_sofq_cut_15to35px))) + + def test_fft_testdataset1(self): + # given + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.cube = self.test_sofq + + # when + self.test_gui.fft() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(result, self.test_gofr)) + + def test_fft_testdataset2(self): + # given + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.cube = self.test_sofq_cut_10to40px + + # when + self.test_gui.fft() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(result, self.test_gofr_cut_10to40px)) + + def test_fft_testdataset3(self): + # given + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.cube = self.test_sofq_cut_15to35px + + # when + self.test_gui.fft() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(result, self.test_gofr_cut_15to35px)) + + def test_applycutoff_range1(self): + # given + self.test_gui.plot_plane = lambda *a, **b: () + self.test_gui.cube = self.test_sofq + self.test_gui.qminentry.insert(0, "10") + self.test_gui.qmaxentry.insert(0, "40") + + # when + self.test_gui.applycutoff() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(np.nan_to_num(result), np.nan_to_num(self.test_sofq_cut_10to40px))) + + def test_applycutoff_range2(self): + # given + self.test_gui.plot_plane = lambda *a, **b: () + self.test_gui.cube = self.test_sofq + self.test_gui.qminentry.insert(0, "15") + self.test_gui.qmaxentry.insert(0, "35") + + # when + self.test_gui.applycutoff() + result = self.test_gui.cube + + # then + self.assertTrue(np.allclose(np.nan_to_num(result), np.nan_to_num(self.test_sofq_cut_15to35px))) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/testdata/__init__.py b/tests/testdata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/testdata/dummydata.h5 b/tests/testdata/dummydata.h5 new file mode 100644 index 0000000..e6763f5 Binary files /dev/null and b/tests/testdata/dummydata.h5 differ diff --git a/tests/testdata/gofr.h5 b/tests/testdata/gofr.h5 new file mode 100644 index 0000000..ef5e93f Binary files /dev/null and b/tests/testdata/gofr.h5 differ diff --git a/tests/testdata/gofr_.h5 b/tests/testdata/gofr_.h5 new file mode 100644 index 0000000..d860773 Binary files /dev/null and b/tests/testdata/gofr_.h5 differ diff --git a/tests/testdata/gofr_cut.h5 b/tests/testdata/gofr_cut.h5 new file mode 100644 index 0000000..6a9096f Binary files /dev/null and b/tests/testdata/gofr_cut.h5 differ diff --git a/tests/testdata/gofr_from_sofq_cut_10to40px.h5 b/tests/testdata/gofr_from_sofq_cut_10to40px.h5 new file mode 100644 index 0000000..583c0f9 Binary files /dev/null and b/tests/testdata/gofr_from_sofq_cut_10to40px.h5 differ diff --git a/tests/testdata/gofr_from_sofq_cut_15to35px.h5 b/tests/testdata/gofr_from_sofq_cut_15to35px.h5 new file mode 100644 index 0000000..f7cd656 Binary files /dev/null and b/tests/testdata/gofr_from_sofq_cut_15to35px.h5 differ diff --git a/tests/testdata/make_testdata.py b/tests/testdata/make_testdata.py new file mode 100644 index 0000000..fb65ea9 --- /dev/null +++ b/tests/testdata/make_testdata.py @@ -0,0 +1,52 @@ +import h5py +import numpy as np + + +def cutcube(fname_uncut_cube, fname_cut_cube, qmin, qmax): + + cube = h5py.File(fname_uncut_cube, "r")["data"] + + X, Y, Z = cube.shape + sphere = np.ones((X, Y, Z)) + r2_inner = qmin**2 + r2_outer = qmax**2 + XS, YS, ZS = np.meshgrid(np.arange(X), np.arange(Y), np.arange(Z)) + R2 = (XS - X // 2) ** 2 + (YS - Y // 2) ** 2 + (ZS - Z // 2) ** 2 + mask = (R2 <= r2_inner) | (R2 >= r2_outer) + sphere[mask] = np.nan + + f = h5py.File(fname_cut_cube, "w") + f.create_dataset("data", data=cube * sphere) + f.close() + + +def fftcube(fname_reci, fname_real): + + fftholder = h5py.File(fname_reci, "r")["data"] + + fftholder = np.nan_to_num(fftholder) + size = list(fftholder.shape) + fftholder = np.fft.ifftshift(fftholder) + fftholder = np.fft.fftn(fftholder, s=size, norm="ortho") + fftholder = np.fft.fftshift(fftholder) + fftholder = fftholder.real + + f = h5py.File(fname_real, "w") + f.create_dataset("data", data=fftholder) + f.close() + + +def dummydata(fname="dummydata.h5"): + dummydata = np.ones((3, 3, 3)) + + f = h5py.File(fname, "w") + f.create_dataset("data", data=dummydata) + f.close() + + +# cutcube("sofq.h5", "sofq_cut_10to40px.h5", 10, 40) +# cutcube("sofq.h5", "sofq_cut_15to35px.h5", 15, 35) +# fftcube("sofq.h5", "gofr.h5") +# fftcube("sofq_cut_10to40px.h5", "gofr_from_sofq_cut_10to40px.h5") +# fftcube("sofq_cut_15to35px.h5", "gofr_from_sofq_cut_15to35px.h5") +dummydata() diff --git a/tests/testdata/sofq.h5 b/tests/testdata/sofq.h5 new file mode 100644 index 0000000..72327c7 Binary files /dev/null and b/tests/testdata/sofq.h5 differ diff --git a/tests/testdata/sofq_cut_10to40px.h5 b/tests/testdata/sofq_cut_10to40px.h5 new file mode 100644 index 0000000..a116f88 Binary files /dev/null and b/tests/testdata/sofq_cut_10to40px.h5 differ diff --git a/tests/testdata/sofq_cut_15to35px.h5 b/tests/testdata/sofq_cut_15to35px.h5 new file mode 100644 index 0000000..2b5ab43 Binary files /dev/null and b/tests/testdata/sofq_cut_15to35px.h5 differ diff --git a/tests/unit_test.py b/tests/unit_test.py new file mode 100644 index 0000000..43cc91a --- /dev/null +++ b/tests/unit_test.py @@ -0,0 +1,150 @@ +import unittest + +import h5py + +from ..fourigui.fourigui import Gui + + +class TestGui(unittest.TestCase): + def setUp(self): + # set up gui + self.test_gui = Gui() + + # set up dummy data + self.dummydata = h5py.File("tests/testdata/dummydata.h5")["data"] + + def test_init(self): + self.assertFalse(self.test_gui.loaded) + self.assertFalse(self.test_gui.transformed) + self.assertFalse(self.test_gui.cutted) + self.assertFalse(self.test_gui.transcutted) + self.assertFalse(self.test_gui.cutoff.get()) + self.assertFalse(self.test_gui.space.get()) + + def test_load_cube_nothing_loaded(self): + # given + self.test_gui.filename_entry.delete(0, "end") + self.test_gui.filename_entry.insert(0, "tests/testdata/dummydata.h5") + + # when + self.test_gui.load_cube() + + # then + self.assertTrue(self.test_gui.loaded) + + def test_load_cube_something_loaded(self): + # given + self.test_gui.loaded + self.test_gui.filename_entry.delete(0, "end") + self.test_gui.filename_entry.insert(0, "tests/testdata/dummydata.h5") + + # when + self.test_gui.load_cube() + + # then + self.assertTrue(self.test_gui.loaded) + + def test_fft_000(self): + # given + self.test_gui.cube = self.dummydata + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.transformed = False + self.test_gui.transcutted = False + self.test_gui.cutoff.set(0) + + # when + self.test_gui.fft() + + # then + self.assertTrue(self.test_gui.transformed and not self.test_gui.transcutted) + + def test_fft_010(self): + # given + self.test_gui.cube = self.dummydata + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.transformed = False + self.test_gui.transcutted = False + self.test_gui.cutoff.set(1) + + # when + self.test_gui.fft() + + # then + self.assertTrue(not self.test_gui.transformed and self.test_gui.transcutted) + # self.assertTrue(self.test_gui.cutted) + + def test_fft_001(self): + # given + self.test_gui.cube = self.dummydata + self.test_gui.cube_reci = self.dummydata + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.transformed = False + self.test_gui.transcutted = True + self.test_gui.cutoff.set(0) + + # when + self.test_gui.fft() + + # then + self.assertTrue(self.test_gui.transformed and self.test_gui.transcutted) + + def test_fft_011(self): + # given + self.test_gui.cube = self.dummydata + self.test_gui.cube_realcut = self.dummydata + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.transformed = False + self.test_gui.transcutted = True + self.test_gui.cutoff.set(1) + + # when + self.test_gui.fft() + + # then + self.assertTrue(not self.test_gui.transformed and self.test_gui.transcutted) + + def test_fft_101(self): + # given + self.test_gui.cube = self.dummydata + self.test_gui.cube_real = self.dummydata + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.transformed = True + self.test_gui.transcutted = True + self.test_gui.cutoff.set(0) + + # when + self.test_gui.fft() + + # then + self.assertTrue(self.test_gui.transformed and self.test_gui.transcutted) + + def test_fft_111(self): + # given + self.test_gui.cube = self.dummydata + self.test_gui.cube_realcut = self.dummydata + self.test_gui.plot_plane = ( + lambda *a, **b: () + ) # overwrite plot_plane which requires not initialized attribute im + self.test_gui.transformed = True + self.test_gui.transcutted = True + self.test_gui.cutoff.set(1) + + # when + self.test_gui.fft() + + # then + self.assertTrue(self.test_gui.transformed and self.test_gui.transcutted) + + +if __name__ == "__main__": + unittest.main()