├── .gitignore ├── README.md ├── TODO.md ├── demo ├── cross.py ├── disturbance_on_surface.py ├── motion.py ├── physical_optics_testbench.py ├── pinholes.py ├── plane_spherical_intf.py ├── plane_wave_surface.py ├── planewaves.py ├── rain.py ├── standing_wave_surface.py ├── wheel.py └── zp_random.py ├── hallucinator ├── __init__.py ├── clothoid.py ├── contour.py ├── field.py ├── group.py ├── integral.py ├── interactive.py ├── mortalsampler.py ├── obj.py ├── obj3.py ├── optics.py ├── paraobj.py ├── perspective.py ├── plotting.py ├── render.py ├── scene.py ├── timereversal.py ├── transform.py ├── treasury.py ├── util.py ├── wave.py └── zoneplate.py ├── profile └── scenes.py ├── readme ├── birth.png ├── born.png ├── hungry.png ├── pairproduction.png ├── pattern1.png ├── pattern2.png ├── pattern3.png ├── pattern4.png ├── pinch.png ├── pinch_rainbow.png ├── pinch_tornado.png ├── soup.png ├── ui.png ├── waves.png ├── waves2.png └── yingyang.png ├── test ├── 2d_test.py ├── __init__.py ├── clothoid.py ├── deprecated │ ├── 2d_wave_gradient.py │ ├── 3d_camera_test.py │ ├── __init__.py │ ├── arrows.py │ ├── camera_path.py │ ├── camera_position.py │ ├── colors.py │ ├── conditional_region.py │ ├── de_field.py │ ├── disturbance_3d.py │ ├── disturbance_on_path.py │ ├── experiment.py │ ├── gradient.py │ ├── harmonic.py │ ├── lattice_test.py │ ├── more_ripples.py │ ├── new_optics_test.py │ ├── obj_test.py │ ├── optics_phase_test.py │ ├── optics_test.py │ ├── optics_test_3d.py │ ├── parameter_test.py │ ├── path_grad_vid.py │ ├── path_gradient.py │ ├── path_test.py │ ├── phasors.py │ ├── plane.py │ ├── polygon_test.py │ ├── ripples.py │ ├── shear_3d.py │ ├── sphere.py │ ├── superzp.py │ ├── surface.py │ ├── textured_path.py │ ├── textured_surface.py │ ├── transform_3_test.py │ ├── transform_test.py │ ├── wave.py │ └── weak_projection_test.py ├── grouptest.py ├── hetero.py ├── lattice.py ├── mortaltest.py ├── ripple_surface.py ├── sphere.py ├── spiral.py ├── wavy_surface.py ├── wireframe.py ├── wireframe_test.py └── zonepinch.py └── ui ├── __init__.py ├── _test.py ├── application.py ├── controls.py ├── deprecated ├── __init__.py ├── application.py ├── controls-functional-2020-7-27.py ├── controls.py ├── dialogs.py ├── display.py ├── viz_3d.py └── wizards.py ├── display.py ├── objects.py ├── static ├── __init__.py └── close.gif ├── tks.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.profile 2 | *.calltree 3 | demo/videos/ 4 | 5 | 6 | # Created by .ignore support plugin (hsz.mobi) 7 | ### Python template 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | *.egg-info/ 31 | .installed.cfg 32 | *.egg 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *,cover 53 | .hypothesis/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # IPython Notebook 77 | .ipynb_checkpoints 78 | 79 | # pyenv 80 | .python-version 81 | 82 | # celery beat schedule file 83 | celerybeat-schedule 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | ### VirtualEnv template 98 | # Virtualenv 99 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 100 | .Python 101 | [Bb]in 102 | [Ii]nclude 103 | [Ll]ib 104 | [Ll]ib64 105 | [Ll]ocal 106 | [Ss]cripts 107 | pyvenv.cfg 108 | .venv 109 | pip-selfcheck.json 110 | ### JetBrains template 111 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 112 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 113 | 114 | # User-specific stuff: 115 | .idea/workspace.xml 116 | .idea/tasks.xml 117 | .idea/dictionaries 118 | .idea/vcs.xml 119 | .idea/jsLibraryMappings.xml 120 | 121 | # Sensitive or high-churn files: 122 | .idea/dataSources.ids 123 | .idea/dataSources.xml 124 | .idea/dataSources.local.xml 125 | .idea/sqlDataSources.xml 126 | .idea/dynamic.xml 127 | .idea/uiDesigner.xml 128 | 129 | # Gradle: 130 | .idea/gradle.xml 131 | .idea/libraries 132 | 133 | # Mongo Explorer plugin: 134 | .idea/mongoSettings.xml 135 | 136 | .idea/ 137 | 138 | ## File-based project format: 139 | *.iws 140 | 141 | ## Plugin-specific files: 142 | 143 | # IntelliJ 144 | /out/ 145 | 146 | # mpeltonen/sbt-idea plugin 147 | .idea_modules/ 148 | 149 | # JIRA plugin 150 | atlassian-ide-plugin.xml 151 | 152 | # Crashlytics plugin (for Android Studio and IntelliJ) 153 | com_crashlytics_export_strings.xml 154 | crashlytics.properties 155 | crashlytics-build.properties 156 | fabric.properties -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Zone plates and pinch zones. Code written in the dead of night. Meant to be read with closed eyes. 2 | 3 | ## Demo 4 | 5 | ![](readme/ui.png) 6 | ![](readme/waves.png) 7 | ![](readme/waves2.png) 8 | 9 | ![](readme/pairproduction.png) 10 | ![](readme/yingyang.png) 11 | ![](readme/soup.png) 12 | 13 | ![](readme/pinch_rainbow.png) 14 | ![](readme/pinch_tornado.png) 15 | 16 | ![](readme/hungry.png) 17 | ![](readme/birth.png) 18 | ![](readme/born.png) 19 | 20 | ![](readme/pattern1.png) 21 | ![](readme/pattern2.png) 22 | ![](readme/pattern3.png) 23 | ![](readme/pattern4.png) 24 | 25 | ![](readme/pinch.png) 26 | 27 | 28 | ## Profiling 29 | 30 | 31 | ``` 32 | python -m cProfile -o profile.profile script.py` 33 | pip install snakeviz 34 | snakeviz profile.profile 35 | ``` 36 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # 6/13/20 2 | 3 | Combine zoneplate, perspective, and contour files? 4 | 5 | # 6/5/20 6 | 7 | aliasing on pinch zones, 3 pinches. Do all generate zones&pinches? Or self-similar? 8 | generalize 9 | better sampling by grid: 10 | need to make points sampled always the same. 11 | the grid should be shifted over exactly, not approximately 12 | 13 | the pinch zones come from moire between the zone plate and the perspective warped zone plate 14 | and the moire with the sampling pixels reveals complexity 15 | what about pinch zone and pinch zone? How might I warp it? A fresnel hyperbola... 16 | 17 | Features 18 | Contour maps for parabolas (fourier, fresnel), circles, ellipse. Compare to zone plates 19 | 20 | Code 21 | Improve argument specification for perspective code 22 | Profile perspective code 23 | 24 | Explore 25 | Opencv keyboard interaction for zone plate scene exploration? (or is matplotlib enough?) Change xy plane range? 26 | https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_gui/py_mouse_handling/py_mouse_handling.html 27 | Helper function for stacking and displaying images? 28 | 29 | Display 30 | Helper function for interpolation video, filenames? 31 | Non-square videos 32 | Parameters on first frame of video? 33 | https://www.geeksforgeeks.org/python-opencv-cv2-puttext-method/ 34 | 35 | 36 | # 10/26/19 37 | 38 | function to modify existing surface/path object with texture 39 | 40 | fix repeated code for adding disturbances 41 | 42 | 3d diffeq fields 43 | diffeq translation methods / class 44 | 45 | scene types: 46 | full color (x, y, z, (R, G, B)) 47 | 48 | group/paraobject 49 | make density individual attribute 50 | make density variable 51 | autonaming 52 | transformations 53 | mirroring 54 | projection 55 | pinhole 56 | 57 | scene 58 | RGB scene 59 | autonaming 60 | 61 | object 62 | go through everything and make sure it all follows one convention 63 | ellipses 64 | solid shapes and surfaces 65 | rectangle, circle etc in 3d 66 | convert 2d to flat 3d 67 | change vectors to use transforms instead of lambdas? 68 | vertices w adjacency 69 | 70 | sampler 71 | random sampling 72 | planes & other parametric -- find bounds based on transform & perspective 73 | clipping 74 | 75 | 76 | filled surfaces 77 | obstructing objects 78 | 79 | optics 80 | ray 81 | shading 82 | lenses, mirrors 83 | waves 84 | integrate 85 | interference from >2 sources 86 | diffraction 87 | testbench 88 | symbols 89 | 90 | 91 | BUGS 92 | 93 | wavelengths arent correct? 94 | images also flipped 90 degrees? 95 | videos switch red/blue and rotated 90 degrees 96 | video dimensions error unless square? 97 | 98 | -------------------------------------------------------------------------------- /demo/cross.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | scene = hl.MonochromeScene() 9 | 10 | f = hl.sin_wave(amplitude=0.3, frequency=6) 11 | 12 | disturbance = hl.propagating_disturbance_2d(f, v=2) 13 | 14 | surface = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 15 | v1=(0, 1, 0), 16 | v2=(0, 0, 1)), 17 | a_range=(-5, 5), 18 | b_range=(-5, 5)) 19 | 20 | surface.add_disturbance(disturbance=disturbance, 21 | init_pos=(0, 0), 22 | polarization=(-1, 0, 0)) 23 | 24 | scene.add_object(hl.cross(width=1, depth=1, span=2, l_height=4, u_height=2, origin=(0, 0, 0)), 25 | name="cross") 26 | 27 | scene.add_object(surface, name="basin") 28 | 29 | x_transl = 0 30 | y_transl = 0 31 | z_transl = 10 32 | 33 | camera_pos = hl.IDENTITY4 34 | 35 | 36 | def rotate(theta, axis): 37 | return np.matmul(camera_pos, hl.rotate_3(theta, axis)) 38 | 39 | 40 | translate = np.matmul(camera_pos, hl.translate_3(x_transl, y_transl, z_transl)) 41 | 42 | 43 | def frame(t, scene): 44 | return scene.render_scene(params={'basin': {'t': t}}, 45 | x_range=(-25, 25), 46 | y_range=(-25, 25), 47 | camera_position=np.matmul(np.matmul(camera_pos, 48 | rotate(math.pi / 6, (0, 1, 0))), 49 | translate), 50 | resolution=50, 51 | projection_type="weak", 52 | styles={'basin': 'line'}, 53 | region_params={'basin': {'a_spacing': 0.2, 54 | 'b_spacing': 0.2, 55 | 'toggle_b': False}}, 56 | foreground=hl.WHITE, 57 | background=hl.RED, 58 | display=False) 59 | 60 | 61 | hl.video2(frame_function=lambda t: frame(t, scene), 62 | filename='./videos/cross', 63 | frame_arguments=np.linspace(0, 10, 70), 64 | fps=7, 65 | parallel=True) 66 | 67 | '''hl.video(frame_func=lambda t: frame(t, scene), 68 | filename='crosstest', 69 | t_range=(0, 10), 70 | FPS=20)''' 71 | -------------------------------------------------------------------------------- /demo/disturbance_on_surface.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | 9 | scene = hl.MonochromeScene() 10 | 11 | f = hl.damped_harmonic(amplitude=0.4, frequency=10, damping_coeff=-0.2) 12 | 13 | disturbance = hl.propagating_disturbance_2d(f, v=2) 14 | 15 | surface = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 16 | v1=(0, 1, 0), 17 | v2=(0, 0, 1)), 18 | a_range=(-5, 5), 19 | b_range=(-5, 5)) 20 | 21 | '''surface.add_disturbance(disturbance=disturbance, 22 | init_pos=(3, 0), 23 | polarization=(-1, 0, 0), 24 | start_time=2) 25 | 26 | surface.add_more_disturbances(disturbance=disturbance, 27 | init_pos=(-3, 0), 28 | polarization=(-1, 0, 0), 29 | start_time=3)''' 30 | 31 | scene.add_object(surface, name="basin") 32 | 33 | x_transl = 0 34 | y_transl = 0 35 | z_transl = 10 36 | 37 | camera_pos = hl.IDENTITY4 38 | 39 | 40 | def rotate(theta, axis): 41 | return np.matmul(camera_pos, hl.rotate_3(theta, axis)) 42 | 43 | 44 | translate = np.matmul(camera_pos, hl.translate_3(x_transl, y_transl, z_transl)) 45 | 46 | '''params={'basin': {'t': t}},''' 47 | hl.video2(frame_function=lambda t: scene.render_scene(x_range=(-25, 25), 48 | y_range=(-25, 25), 49 | camera_position=np.matmul(np.matmul(np.matmul(camera_pos, 50 | rotate( 51 | theta=t * math.pi / 10, 52 | axis=(1, 0, 0))), 53 | rotate(math.pi / 6, (0, 1, 0))), 54 | translate), 55 | resolution=50, 56 | projection_type="weak", 57 | style='line', 58 | region_params={'a_spacing': 0.2, 59 | 'b_spacing': 0.2, 60 | 'toggle_b': False}, 61 | foreground=hl.WHITE, 62 | background=hl.BLACK, 63 | display=False), 64 | filename='surface_test_rotate', 65 | frame_arguments=np.linspace(0, 10, 70), 66 | fps=7, 67 | parallel=True) 68 | -------------------------------------------------------------------------------- /demo/motion.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import numpy as np 4 | import hallucinator as hl 5 | import random 6 | 7 | 8 | def sparkle(h, w, frames, repetions=1): 9 | h = random.randint(0, h-1) 10 | w = random.randint(0, w-1) 11 | t = [] 12 | for i in range(repetions): 13 | t.append(random.randint(0, frames-duration)) 14 | return h, w, t 15 | 16 | 17 | height = 700 18 | width = 700 19 | num_frames = 500 20 | num_sparkles = 50000 21 | duration = 3 22 | repetitions = 2 23 | 24 | frames = [] 25 | 26 | 27 | for i in range(num_frames): 28 | frames.append(np.zeros((height, width))) 29 | 30 | for i in range(num_sparkles): 31 | h, w, t = sparkle(height, width, num_frames, repetitions) 32 | for time in t: 33 | for j in range(duration): 34 | frames[time+j][h][w] = random.randint(20, 255) 35 | frames[time] = frames[time].astype(np.uint8) 36 | 37 | 38 | def return_frame(frame): 39 | return frames[frame] 40 | 41 | 42 | frame_arguments = range(num_frames) 43 | 44 | hl.video2(frame_function=return_frame, 45 | frame_arguments=frame_arguments, 46 | filename="./videos/gradient_sparkles", 47 | fps=15) 48 | 49 | -------------------------------------------------------------------------------- /demo/physical_optics_testbench.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | 5 | import hallucinator as hl 6 | from tkinter import * 7 | from tkinter import messagebox 8 | from tkinter import ttk 9 | 10 | 11 | def add_source_window(position=END): 12 | def add_source(): 13 | if not position == END: 14 | scheduled_listbox.delete(position) 15 | if type.get() == "Point": 16 | new_source = hl.PointSource(source=(float(x_pos.get()), float(y_pos.get())), 17 | frequency=float(frequency.get()), 18 | amplitude=float(amplitude.get()), 19 | phase_offset=float(phase_offset.get()), 20 | velocity=float(velocity.get())) 21 | else: 22 | pass 23 | sources.append(new_source) 24 | scheduled_listbox.insert(END, 'source') 25 | window.destroy() 26 | 27 | window = Toplevel(f) 28 | Label(window, text="source type").grid(row=0, column=0) 29 | Label(window, text="x position (m)").grid(row=1, column=0) 30 | Label(window, text="y position (m)").grid(row=1, column=2) 31 | Label(window, text="frequency (hertz)").grid(row=2, column=0) 32 | Label(window, text="amplitude").grid(row=2, column=2) 33 | Label(window, text="phase offset (0-2pi)").grid(row=3, column=0) 34 | Label(window, text="velocity (m/s)").grid(row=3, column=2) 35 | 36 | type = ttk.Combobox(window, 37 | values=["Point", 38 | "Plane"]) 39 | type.grid(row=0, column=1) 40 | x_pos = Entry(window) 41 | y_pos = Entry(window) 42 | frequency = Entry(window) 43 | amplitude = Entry(window) 44 | phase_offset = Entry(window) 45 | velocity = Entry(window) 46 | 47 | velocity.insert(END, 300000000) 48 | phase_offset.insert(END, 0) 49 | frequency.insert(END, hl.RED_F) 50 | amplitude.insert(END, 1) 51 | 52 | x_pos.grid(row=1, column=1) 53 | y_pos.grid(row=1, column=3) 54 | frequency.grid(row=2, column=1) 55 | amplitude.grid(row=2, column=3) 56 | phase_offset.grid(row=3, column=1) 57 | velocity.grid(row=3, column=3) 58 | submit_b = Button(window, text="SUBMIT", command=add_source) 59 | submit_b.grid(row=4, column=2) 60 | cancel_b = Button(window, text="CANCEL", command=window.destroy) 61 | cancel_b.grid(row=4, column=1) 62 | 63 | 64 | def edit_source(): 65 | pass 66 | 67 | 68 | def remove_source(): 69 | pass 70 | 71 | 72 | def add_region(): 73 | pass 74 | 75 | 76 | def remove_region(): 77 | pass 78 | 79 | 80 | def show_preview(): 81 | pass 82 | 83 | 84 | def render(): 85 | pass 86 | 87 | 88 | sources = [] 89 | regions = [] 90 | 91 | master = Tk() 92 | 93 | f = Frame(master, height=500, width=1000) 94 | f.pack_propagate(0) # don't shrink 95 | f.pack() 96 | 97 | sched_new = Button(f, text="ADD SOURCE", command=add_source_window) 98 | sched_new.grid(row=0, column=0, padx=5, pady=10) 99 | 100 | edit = Button(f, text="EDIT", command=edit_source) 101 | edit.grid(row=0, column=1, padx=5, pady=10) 102 | 103 | edit = Button(f, text="REMOVE", command=remove_source) 104 | edit.grid(row=0, column=2, padx=5, pady=10) 105 | 106 | scheduled_listbox = Listbox(f, width=50, selectmode=SINGLE) 107 | scheduled_listbox.grid(row=1, column=0, columnspan=5, padx=5, pady=10) 108 | 109 | add_new_account = Button(f, text="ADD REGION", command=add_region) 110 | add_new_account.grid(row=2, column=0, padx=5, pady=10) 111 | 112 | logout = Button(f, text="REMOVE REGION", command=remove_region) 113 | logout.grid(row=2, column=1, padx=5, pady=10) 114 | 115 | agents_listbox = Listbox(f, width=50, 116 | selectmode=SINGLE) 117 | agents_listbox.grid(row=3, column=0, columnspan=5, padx=5, pady=10) 118 | 119 | logout = Button(f, text="SHOW PREVIEW", command=show_preview) 120 | logout.grid(row=4, column=0, padx=5, pady=10) 121 | logout = Button(f, text="RENDER", command=render) 122 | logout.grid(row=4, column=1, padx=5, pady=10) 123 | 124 | mainloop() 125 | -------------------------------------------------------------------------------- /demo/pinholes.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | 9 | def make_pinholes(distance, wavelength, phase_diff, sampling_density, separation): 10 | surface = lambda a, b: (a, b, distance) 11 | source1 = (-separation, 0, 0) 12 | source2 = (separation, 0, 0) 13 | 14 | pin1 = hl.PointSource(source=source1, wavelength=wavelength, amplitude=1, init_phase=0) 15 | 16 | pin2 = hl.PointSource(source=source2, wavelength=wavelength, amplitude=1, init_phase=phase_diff) 17 | 18 | points = hl.eval_surface_intensity(sources=(pin1, pin2), 19 | surface=surface, 20 | a_range=(-30, 30), 21 | b_range=(-30, 30), 22 | a_density=sampling_density, 23 | b_density=sampling_density) 24 | 25 | canv = hl.set_to_gradient(points, 26 | x_range=(-30, 30), 27 | y_range=(-30, 30), 28 | black_ref=0, 29 | white_ref=4.0, 30 | default=hl.GRAY, 31 | resolution=50) 32 | 33 | return canv 34 | 35 | 36 | '''canv = make_pinholes(distance=10, 37 | wavelength=0.001, 38 | phase_diff=0, 39 | sampling_density=20, 40 | separation=0.1) 41 | 42 | hl.render_from_array(canv) 43 | hl.save_img(canv, 'two_pinhole_d10-w0.001-s0.1-sd20')''' 44 | 45 | 46 | hl._deprecated_video(frame_func=lambda t: make_pinholes(distance=10, 47 | wavelength=0.001, 48 | phase_diff=0, 49 | sampling_density=t**2, 50 | separation=0.1), 51 | filename='pinholes_2', 52 | t_range=(0, 7), 53 | FPS=5) 54 | 55 | -------------------------------------------------------------------------------- /demo/plane_spherical_intf.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | 9 | def make_zone_plate(distance, wavelength, phase_diff, a_density, b_density, direction, x_range, y_range): 10 | surface = lambda a, b: (a, b, distance) 11 | source = (0, 0, 0) 12 | 13 | planewave = hl.PlaneWave(source=source, wavelength=wavelength, amplitude=1, init_phase=0, direction=direction) 14 | 15 | spherewave = hl.PointSource(source=source, wavelength=wavelength, amplitude=1, init_phase=phase_diff) 16 | 17 | points = hl.eval_surface_intensity(sources=(planewave, spherewave), 18 | surface=surface, 19 | a_range=x_range, 20 | b_range=y_range, 21 | a_density=a_density, 22 | b_density=b_density) 23 | 24 | canv = hl.set_to_gradient(points, 25 | x_range=x_range, 26 | y_range=y_range, 27 | black_ref=0, 28 | white_ref=4.0, 29 | default=hl.GRAY, 30 | resolution=30) 31 | 32 | return canv 33 | 34 | 35 | distance = 20 36 | wavelength = 0.1 37 | phase_diff = 0 38 | a_density = 30 39 | b_density = 30 40 | direction = (0, 0, 1) 41 | x_range = (-20, 20) 42 | y_range = (-20, 20) 43 | 44 | zp = make_zone_plate(distance=distance, 45 | wavelength=wavelength, 46 | phase_diff=phase_diff, 47 | a_density=a_density, 48 | b_density=b_density, 49 | direction=direction, 50 | x_range=x_range, 51 | y_range=y_range) 52 | 53 | hl.render_from_array(zp) 54 | 55 | hl.save_img(zp, 'zoneplates/zoneplate_d{0}-w{1}-p{2}-a{3}-b{4}-dir{5}-xr{6}-yr{7}'.format(distance, wavelength, phase_diff, 56 | a_density, b_density, direction, x_range, y_range)) 57 | 58 | ''' 59 | hl.video(frame_func=lambda t: make_zone_plate(distance=distance, 60 | wavelength=wavelength, 61 | phase_diff=phase_diff, 62 | a_density=a_density, 63 | b_density=b_density*t, 64 | direction=direction), 65 | filename='zoneplates/zone_plate_change_b_density', 66 | t_range=(1, 5), 67 | FPS=10)''' 68 | -------------------------------------------------------------------------------- /demo/plane_wave_surface.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | scene = hl.MonochromeScene() 9 | 10 | f = hl.sin_wave(amplitude=0.4, frequency=3) 11 | 12 | disturbance = hl.plane_wave(f, v=2, direction=(1, 0)) 13 | 14 | surface = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 15 | v1=(0, 1, 0), 16 | v2=(0, 0, 1)), 17 | a_range=(-10, 10), 18 | b_range=(-10, 10)) 19 | 20 | surface.add_disturbance(disturbance=disturbance, 21 | init_pos=(3, 0), 22 | polarization=(-1, 0, 0), 23 | start_time=0) 24 | 25 | scene.add_object(surface, name="basin") 26 | 27 | x_transl = 0 28 | y_transl = 0 29 | z_transl = 10 30 | 31 | camera_pos = hl.IDENTITY4 32 | 33 | 34 | def rotate(theta, axis): 35 | return np.matmul(camera_pos, hl.rotate_3(theta, axis)) 36 | 37 | 38 | translate = np.matmul(camera_pos, hl.translate_3(x_transl, y_transl, z_transl)) 39 | 40 | hl.video2(frame_function=lambda t: scene.render_scene(params={'basin': {'t': t}}, 41 | x_range=(-20, 20), 42 | y_range=(-20, 20), 43 | camera_position=np.matmul(np.matmul(np.matmul(camera_pos, 44 | rotate( 45 | theta=t * math.pi / 10, 46 | axis=(1, 0, 0))), 47 | rotate(math.pi / 8, (0, 1, 0))), 48 | translate), 49 | resolution=50, 50 | projection_type="weak", 51 | style='line', 52 | region_params={'a_spacing': 0.2, 53 | 'b_spacing': 0.2, 54 | 'toggle_b': False}, 55 | foreground=hl.WHITE, 56 | background=hl.BLACK, 57 | display=False), 58 | filename='./videos/plane_wave', 59 | frame_arguments=np.linspace(0, 10, 50), 60 | fps=7, 61 | parallel=True) -------------------------------------------------------------------------------- /demo/planewaves.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | 9 | def plane_interference(distance, wavelength, phase_diff, sampling_density, a_direction, b_direction): 10 | surface = lambda a, b: (a, b, distance) 11 | source1 = (0, 0, 0) 12 | source2 = (0, 0, 0) 13 | 14 | pin1 = hl.PlaneWave(source=source1, wavelength=wavelength, amplitude=1, init_phase=0, direction=a_direction) 15 | 16 | pin2 = hl.PlaneWave(source=source2, wavelength=wavelength, amplitude=1, init_phase=phase_diff, 17 | direction=b_direction) 18 | 19 | points = hl.eval_surface_intensity(sources=(pin1, pin2), 20 | surface=surface, 21 | a_range=(-15, 15), 22 | b_range=(-15, 15), 23 | a_density=sampling_density, 24 | b_density=sampling_density) 25 | 26 | canv = hl.set_to_gradient(points, 27 | x_range=(-15, 15), 28 | y_range=(-15, 15), 29 | black_ref=0, 30 | white_ref=4.0, 31 | default=hl.GRAY, 32 | resolution=50) 33 | 34 | return canv 35 | 36 | 37 | angle = math.pi / 180 38 | a_direction = (0, math.sin(angle), math.cos(angle)) 39 | b_direction = (0, -math.sin(angle), math.cos(angle)) 40 | distance = 10 41 | wavelength = 0.1 42 | phase_diff = 0 43 | 44 | canv = plane_interference(distance=distance, 45 | wavelength=wavelength, 46 | phase_diff=phase_diff, 47 | sampling_density=20, 48 | a_direction=a_direction, 49 | b_direction=b_direction) 50 | 51 | hl.render_from_array(canv) 52 | hl.save_img(canv, 'planewaves/planewaves_{0}-w{1}-p{2}-a{3}'.format(distance, wavelength, phase_diff, 53 | angle)) 54 | 55 | -------------------------------------------------------------------------------- /demo/rain.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | import random 8 | 9 | scene = hl.MonochromeScene() 10 | 11 | f = hl.damped_harmonic(amplitude=0.2, frequency=20, damping_coeff=3) 12 | disturbance = hl.propagating_disturbance_2d(f, v=2, fade_factor=0.5) 13 | 14 | surface = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 15 | v1=(0, 1, 0), 16 | v2=(0, 0, 1)), 17 | a_range=(-5, 5), 18 | b_range=(-5, 5)) 19 | 20 | num_drops = 30 21 | drops = [] 22 | surface.add_disturbance(disturbance=disturbance, 23 | init_pos=(random.uniform(-5, 5), random.uniform(-5, 5)), 24 | polarization=(-1, 0, 0), 25 | start_time=random.uniform(0, 5)) 26 | 27 | for i in range(num_drops): 28 | start_time = random.uniform(i/3, 10) 29 | x_location = random.uniform(-5, 5) 30 | y_location = random.uniform(-5, 5) 31 | surface.add_more_disturbances(disturbance=disturbance, 32 | init_pos=(x_location, y_location), 33 | polarization=(-1, 0, 0), 34 | start_time=start_time) 35 | 36 | scene.add_object(surface, name="basin") 37 | 38 | camera_pos = hl.IDENTITY4 39 | camera_pos = np.matmul(camera_pos, hl.translate_3(-5, 0, -15)) 40 | camera_pos = np.matmul(camera_pos, hl.rotate_3(math.pi / 3, axis=(0, 1, 0))) 41 | 42 | 43 | rotation_per_timestep = hl.rotate_3(math.pi / 20, axis=(1, 0, 0)) 44 | 45 | 46 | hl.parallel_video(frame_func=lambda t: scene.render_scene(params={'basin': {'t': t}}, 47 | x_range=(-25, 25), 48 | y_range=(-25, 25), 49 | camera_position=camera_pos, 50 | resolution=50, 51 | projection_type="weak", 52 | style='line', 53 | region_params={'a_spacing': 0.15, 54 | 'b_spacing': 0.15, 55 | 'toggle_b': False}, 56 | foreground=hl.WHITE, 57 | background=hl.BLACK, 58 | display=False), 59 | filename='rain_5', 60 | t_range=(0, 5), 61 | fps=8, 62 | is_color=True) 63 | -------------------------------------------------------------------------------- /demo/standing_wave_surface.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | scene = hl.MonochromeScene() 9 | 10 | f = hl.sin_wave(amplitude=0.1, frequency=1) 11 | f2 = hl.sin_wave(amplitude=0.1, frequency=2) 12 | f3 = hl.sin_wave(amplitude=0.1, frequency=3) 13 | f4 = hl.sin_wave(amplitude=0.1, frequency=4) 14 | 15 | disturbance = hl.plane_wave(f, v=0.5, direction=(1, 0)) 16 | disturbance2 = hl.plane_wave(f2, v=0.5, direction=(-1, 0)) 17 | disturbance3 = hl.plane_wave(f3, v=0.5, direction=(1, 0)) 18 | disturbance4 = hl.plane_wave(f4, v=0.5, direction=(-1, 0)) 19 | disturbance5 = hl.plane_wave(f, v=0.5, direction=(0, 1)) 20 | disturbance6 = hl.plane_wave(f2, v=0.5, direction=(0, -1)) 21 | disturbance7 = hl.plane_wave(f3, v=0.5, direction=(0, 1)) 22 | disturbance8 = hl.plane_wave(f4, v=0.5, direction=(0, -1)) 23 | 24 | 25 | surface = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 26 | v1=(0, 1, 0), 27 | v2=(0, 0, 1)), 28 | a_range=(-5, 5), 29 | b_range=(-5, 5)) 30 | 31 | surface.add_disturbance(disturbance=disturbance, 32 | init_pos=(3, 0), 33 | polarization=(-1, 0, 0), 34 | start_time=0) 35 | 36 | surface.add_more_disturbances(disturbance=disturbance2, 37 | init_pos=(-3, 0), 38 | polarization=(-1, 0, 0), 39 | start_time=0) 40 | 41 | surface.add_more_disturbances(disturbance=disturbance3, 42 | init_pos=(-3, 0), 43 | polarization=(-1, 0, 0), 44 | start_time=0) 45 | 46 | 47 | surface.add_more_disturbances(disturbance=disturbance4, 48 | init_pos=(-3, 0), 49 | polarization=(-1, 0, 0), 50 | start_time=0) 51 | 52 | surface.add_more_disturbances(disturbance=disturbance5, 53 | init_pos=(-3, 0), 54 | polarization=(-1, 0, 0), 55 | start_time=0) 56 | surface.add_more_disturbances(disturbance=disturbance6, 57 | init_pos=(-3, 0), 58 | polarization=(-1, 0, 0), 59 | start_time=0) 60 | surface.add_more_disturbances(disturbance=disturbance7, 61 | init_pos=(-3, 0), 62 | polarization=(-1, 0, 0), 63 | start_time=0) 64 | surface.add_more_disturbances(disturbance=disturbance8, 65 | init_pos=(-3, 0), 66 | polarization=(-1, 0, 0), 67 | start_time=0) 68 | 69 | scene.add_object(surface, name="basin") 70 | 71 | x_transl = 0 72 | y_transl = 0 73 | z_transl = 10 74 | 75 | camera_pos = hl.IDENTITY4 76 | 77 | 78 | def rotate(theta, axis): 79 | return np.matmul(camera_pos, hl.rotate_3(theta, axis)) 80 | 81 | 82 | translate = np.matmul(camera_pos, hl.translate_3(x_transl, y_transl, z_transl)) 83 | 84 | camera_pos = np.matmul(rotate(math.pi / 4, (0, 1, 0)), translate) 85 | 86 | hl.video2(frame_function=lambda t: scene.render_scene(params={'basin': {'t': t}}, 87 | x_range=(-25, 25), 88 | y_range=(-25, 25), 89 | camera_position=camera_pos, 90 | resolution=50, 91 | projection_type="weak", 92 | style='line', 93 | region_params={'a_spacing': 0.2, 94 | 'b_spacing': 0.2, 95 | 'toggle_b': False}, 96 | foreground=hl.WHITE, 97 | background=hl.BLACK, 98 | display=False), 99 | filename='./videos/standing_wave_harmonics_2d', 100 | frame_arguments=np.linspace(0, 20, 50), 101 | fps=7, 102 | parallel=True) -------------------------------------------------------------------------------- /demo/wheel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import math 4 | 5 | sys.path.append('../hallucinator') 6 | import hallucinator as hl 7 | 8 | 9 | def rotating_wheel(t, frequency): 10 | canvas = hl.MonochromeScene() 11 | canvas.add_object(hl.wheel(radius=1, num_spokes=10).rotate(theta=math.pi * 2 * frequency * t), "wheel") 12 | 13 | return canvas.render_scene(x_range=(-2, 2), 14 | y_range=(-2, 2), 15 | resolution=200, 16 | density=30, 17 | foreground=hl.WHITE, 18 | background=hl.BLACK, 19 | display=False) 20 | 21 | 22 | frequency = 2.3 23 | 24 | hl._deprecated_video(frame_func=lambda t: rotating_wheel(t, frequency), 25 | filename='rotating_wheel_frequency_{0}'.format(frequency), 26 | t_range=(0, 1), 27 | FPS=20) -------------------------------------------------------------------------------- /demo/zp_random.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | 9 | def make_zone_plate(distance, wavelength, phase_diff, density, direction): 10 | surface = lambda a, b: (a, b, distance) 11 | source = (0, 0, 0) 12 | 13 | planewave = hl.PlaneWave(source=source, wavelength=wavelength, amplitude=1, init_phase=0, direction=direction) 14 | 15 | spherewave = hl.PointSource(source=source, wavelength=wavelength, amplitude=1, init_phase=phase_diff) 16 | 17 | points = hl.eval_surface_intensity_random(sources=(planewave, spherewave), 18 | surface=surface, 19 | a_range=(-15, 15), 20 | b_range=(-15, 15), 21 | density=density) 22 | 23 | canv = hl.set_to_gradient(points, 24 | x_range=(-15, 15), 25 | y_range=(-15, 15), 26 | black_ref=0, 27 | white_ref=4.0, 28 | default=hl.GRAY, 29 | resolution=50) 30 | 31 | return canv 32 | 33 | 34 | distance = 15 35 | wavelength = 0.05 36 | phase_diff = 0 37 | density = 300 38 | direction = (0, 0, 1) 39 | 40 | zp = make_zone_plate(distance=distance, 41 | wavelength=wavelength, 42 | phase_diff=phase_diff, 43 | density=density, 44 | direction=direction) 45 | 46 | hl.render_from_array(zp) 47 | 48 | hl.save_img(zp, 'zoneplates/zoneplate_rand_d{0}-w{1}-p{2}-d{3}-dir{4}'.format(distance, wavelength, phase_diff, 49 | density, direction)) 50 | 51 | '''hl.video(frame_func=lambda t: make_zone_plate(distance=15, 52 | wavelength=0.1, 53 | phase_diff=0, 54 | sampling_density=15), 55 | filename='zone_plate_change_sample_rate_5', 56 | t_range=(2, 6), 57 | FPS=10)''' 58 | -------------------------------------------------------------------------------- /hallucinator/__init__.py: -------------------------------------------------------------------------------- 1 | from .obj import * 2 | from .mortalsampler import * 3 | from .group import * 4 | from .transform import * 5 | from .obj3 import * 6 | from .render import * 7 | from .scene import * 8 | from .paraobj import * 9 | from .util import * 10 | from .field import * 11 | from .optics import * 12 | from .zoneplate import * 13 | from .perspective import * 14 | from .plotting import * 15 | from .interactive import * 16 | from .contour import * 17 | from .transform import * 18 | from .treasury import * 19 | from .integral import * 20 | from .clothoid import * -------------------------------------------------------------------------------- /hallucinator/clothoid.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import math 3 | 4 | 5 | def clothoid(scene, plot_range=(0, 50), resolution=30, scale=1, center=(0, 0), phase=0): 6 | complex_zp = hl.complex_zp(scale, center, phase) 7 | endpoints = hl.plot_phase_integral(pattern=complex_zp, plot_range=plot_range, resolution=resolution, scene=scene) 8 | return endpoints 9 | 10 | 11 | # returns array with clothoid image 12 | def plot_clothoid(padding=0, plot_range=(0, 50), resolution=20, scale=1, center=(0, 0), phase=0): 13 | scene = hl.MonochromeScene() 14 | 15 | endpoints = clothoid(scene, plot_range=plot_range, resolution=resolution, scale=scale, center=center, phase=phase) 16 | 17 | x_values = [e[0] for e in endpoints] 18 | y_values = [e[1] for e in endpoints] 19 | 20 | max_x = int(math.ceil(max(x_values))) 21 | min_x = int(math.floor(min(x_values))) 22 | max_y = int(math.ceil(max(y_values))) 23 | min_y = int(math.floor(min(y_values))) 24 | 25 | clothoid_arr = scene.render_scene(x_range=(min_x - padding, max_x + padding), 26 | y_range=(min_y - padding, max_y + padding), 27 | resolution=10, densities=15, projection_type=hl.Projections.ORTHO) 28 | 29 | return clothoid_arr 30 | -------------------------------------------------------------------------------- /hallucinator/contour.py: -------------------------------------------------------------------------------- 1 | import math 2 | from pprint import pprint 3 | 4 | import cv2 5 | import numpy as np 6 | import numexpr as ne 7 | import hallucinator as hl 8 | 9 | 10 | # Ellipsoid 1 = x2/a2 + y2/b2 + z2/c2. (a,0,0), (0,b,0), (0,0,c) lie in the surface 11 | def ellipsoid(xy, center=0, a=.5, b=1, **kwargs): 12 | x2y2 = ne.evaluate("(xy-center)**2") 13 | x2 = x2y2[:, :, 0] 14 | y2 = x2y2[:, :, 1] 15 | return ne.evaluate("sqrt(1 - (x2/a + y2/b))") 16 | 17 | 18 | # x2+y2+z2 = 1 19 | def sphere(xy, center=(0, 0), **kwargs): 20 | x2y2 = ne.evaluate("sum((xy-center)**2, axis=2)") 21 | return ne.evaluate("sqrt(1 - x2y2%1)") 22 | 23 | 24 | def perspective_plane(xy, center=(0, 0), z=10, **kwargs): 25 | x2y2 = ne.evaluate("sum((xy-center)**2, axis=2)") 26 | z2 = z**2 27 | scale = 2 * math.pi 28 | return ne.evaluate("sqrt(x2y2+z2)*scale") 29 | 30 | 31 | def fourier_plane(xy, center=(0, 0), **kwargs): 32 | x2y2 = ne.evaluate("sum((xy-center)**2, axis=2)") 33 | return x2y2 34 | 35 | 36 | def inverse_fourier_plane(xy, center=(0, 0), **kwargs): 37 | x2y2 = ne.evaluate("sum((xy-center)**2, axis=2)") 38 | return ne.evaluate("1/(x2y2)") 39 | 40 | 41 | # z=x2-y2 h=c(x,y) * (x2-y2), c = x2+y2+z2 42 | def hyperbolic_paraboloid(xy, center=(0, 0), **kwargs): 43 | x2y2 = ne.evaluate("(xy-center)**2") 44 | x2 = x2y2[:, :, 0] 45 | y2 = x2y2[:, :, 1] 46 | x2my2 = ne.evaluate("x2 - y2") 47 | return x2my2 48 | 49 | 50 | # z=1/x2 - 1/y2 51 | def reverse_paraboloid(xy, center=(0, 0), rotate=0, **kwargs): 52 | x2y2 = ne.evaluate("(xy-center)**2") 53 | x2 = x2y2[:, :, 0] 54 | y2 = x2y2[:, :, 1] 55 | x2my2 = ne.evaluate("1/x2 - 1/y2") 56 | return x2my2 57 | 58 | 59 | # z=1/x2 - 1/y2 60 | def reverse_paraboloid_nosq(xy, center=(0, 0), rotate=0, **kwargs): 61 | x2y2 = ne.evaluate("(xy-center)**2") 62 | x2 = x2y2[:, :, 0] 63 | y2 = x2y2[:, :, 1] 64 | x2my2 = ne.evaluate("1/x - 1/y") 65 | return x2my2 66 | 67 | 68 | # z=x3-3xy2 69 | def monkey_saddle(xy, center=(0, 0), **kwargs): 70 | x = xy[:, :, 0] - center[0] 71 | y = xy[:, :, 1] - center[1] 72 | return ne.evaluate("x**3 - 3 * x * y**2") 73 | 74 | 75 | def cartesian(r, angle): 76 | return np.stack([ne.evaluate("r*cos(angle)"), ne.evaluate("r*sin(angle)")], axis=2) 77 | 78 | 79 | def polar(xy, z2=0): 80 | x2y2 = ne.evaluate("sum(xy**2, axis=2)") 81 | r = ne.evaluate("sqrt(x2y2 + z2)") 82 | x = xy[:, :, 0] 83 | y = xy[:, :, 1] 84 | angle = ne.evaluate("arctan2(y, x)") 85 | return r, angle 86 | 87 | 88 | # pinch = math.pi 89 | # 3 pinch = 4/3 * math.pi 90 | # 2 pinch = 2*math.pi 91 | def pinch_mod(xy, center=(0, 0), mod=4*math.pi/3, **kwargs): 92 | xy = xy - center 93 | # Convert to polar and do the above 94 | r, angle = polar(xy) 95 | # Mod angle, scale to [-pi/2 to pi/2], 96 | scale = math.pi/mod 97 | recenter = math.pi/2 98 | angle = ne.evaluate("angle % mod * scale - recenter") 99 | # Back to cartesian and then pinch 100 | return hyperbolic_paraboloid(cartesian(r, angle)) 101 | 102 | 103 | def contour(array, threshold=2*math.pi): 104 | return ne.evaluate("array % threshold") 105 | 106 | 107 | def main(): 108 | 109 | def get_default_params(): 110 | return { 111 | "func": 0, 112 | "resolution": 1000, 113 | "value_range": np.array([[-1, 1], [-1, 1]], dtype=np.float64), 114 | "threshold": 0.1, 115 | "center": [0, 0], 116 | "rotate": math.pi/12, 117 | "z": 10, 118 | "mod": math.pi, 119 | "hsv": True 120 | } 121 | 122 | def params_string(params): 123 | return f"p: {hl.tupliround(params['center'])} " \ 124 | f"z={params['z']} " \ 125 | f"mod={params['mod']} " \ 126 | f"range: {hl.tupliround(params['value_range'])} " \ 127 | f"hsv={params['hsv']} " \ 128 | f"threshold={params['threshold']} " 129 | 130 | params = get_default_params() 131 | 132 | funcs = [ 133 | sphere, 134 | ellipsoid, 135 | fourier_plane, 136 | perspective_plane, 137 | reverse_paraboloid, 138 | inverse_fourier_plane, 139 | hyperbolic_paraboloid, 140 | monkey_saddle, 141 | pinch_mod, 142 | ] 143 | 144 | def image_func(): 145 | im_funcs = [params["func"]] 146 | 147 | xy = hl.xy_plane(value_range=params["value_range"], resolution=params["resolution"]) 148 | planes = [f(xy=xy, center=(0, 0), mod=math.pi) for f in im_funcs] 149 | shifts = [f(xy=xy, 150 | center=params["center"], 151 | mod=params["mod"], 152 | rotate=params["mod"]) 153 | for f in im_funcs] 154 | # diffs = [p + s for p, s in zip(planes, shifts)] 155 | 156 | planes = [contour(f, threshold=params["threshold"]) for f in planes] 157 | shifts = [-1*contour(f, threshold=-params["threshold"]) for f in shifts] 158 | diffs = [contour(p + s, threshold=params["threshold"]) for p, s in zip(planes, shifts)] 159 | contours = [ 160 | hl.imagify(f, hsv=params["hsv"], bwref=[0, params["threshold"]]) 161 | for f in [*planes, *shifts, *diffs] 162 | ] 163 | 164 | image = hl.tile_images(contours, num_cols=3)#len(im_funcs)) 165 | image = hl.add_text_bar(image, params_string(params)) 166 | return image 167 | 168 | 169 | # (name, range, default) 170 | sliders = [ 171 | #("resolution", [100, 250, 400, 600, 1000]), 172 | ("func", funcs), 173 | ("x", np.linspace(-100, 100, 1000), 0), 174 | ("y", np.linspace(-100, 100, 1000), 0), 175 | ("zoom", np.linspace(0.01, 100, 1000), 1), 176 | ("dx", np.linspace(-10, 10, 200), 0), 177 | ("dy", np.linspace(-10, 10, 200), 0), 178 | ("mod", np.geomspace(0.001, 10*math.pi, 1000), math.pi), 179 | ("hsv", [True, False]) 180 | ] 181 | 182 | def slider_callback(slider_vals): 183 | params.update(slider_vals) 184 | 185 | zoom = slider_vals["zoom"] 186 | x = slider_vals["x"] 187 | y = slider_vals["y"] 188 | params["value_range"] = [[-zoom+x, zoom+x], [-zoom+y, zoom+y]] 189 | params["center"] = [slider_vals["dx"], slider_vals["dy"]] 190 | params["rotate"] = slider_vals["mod"] 191 | 192 | 193 | hl.start_interactive(image_func, sliders=sliders, slider_callback=slider_callback) 194 | 195 | # xy_step = 1/8 196 | # point_step = 0.1 197 | # mod_step = math.pi/64 198 | # 199 | # def key_func(key): 200 | # length = params["value_range"][1][1] - params["value_range"][1][0] 201 | # 202 | # # WASD 203 | # if key == ord("s"): # Image coordinates are reversed! 0,0 is at the top left 204 | # params["value_range"][1] += length*xy_step 205 | # elif key == ord("w"): 206 | # params["value_range"][1] -= length*xy_step 207 | # elif key == ord("d"): 208 | # params["value_range"][0] += length*xy_step 209 | # elif key == ord("a"): 210 | # params["value_range"][0] -= length*xy_step 211 | # 212 | # # ZX 213 | # elif key == ord("z"): 214 | # params["value_range"][:, 0] -= length*xy_step 215 | # params["value_range"][:, 1] += length*xy_step 216 | # elif key == ord("x"): 217 | # params["value_range"][:, 0] += length*xy_step 218 | # params["value_range"][:, 1] -= length*xy_step 219 | # 220 | # # PL;' (like wasd) 221 | # elif key == ord("p"): 222 | # params["center"][1] += point_step 223 | # elif key == ord(";"): 224 | # params["center"][1] -= point_step 225 | # elif key == ord("l"): 226 | # params["center"][0] += point_step 227 | # elif key == ord("'"): 228 | # params["center"][0] -= point_step 229 | # 230 | # elif key == ord("["): 231 | # params["threshold"] -= point_step 232 | # elif key == ord("]"): 233 | # params["threshold"] += point_step 234 | # 235 | # # RE 236 | # elif key == ord("r"): 237 | # params["mod"] += mod_step 238 | # elif key == ord("e"): 239 | # params["mod"] -= mod_step if params["mod"] > mod_step else 0 240 | # 241 | # 242 | # elif key == ord("h"): 243 | # params["hsv"] = not params["hsv"] 244 | # 245 | # elif key == ord("."): 246 | # params["value_range"] = get_default_params()["value_range"] 247 | 248 | # hl.start_interactive(image_func, key_func) 249 | 250 | 251 | 252 | if __name__ == "__main__": 253 | main() 254 | 255 | 256 | ################################################################################ 257 | # Tests 258 | ################################################################################ 259 | 260 | 261 | def test_polar(): 262 | x = np.random.rand(100, 100) 263 | y = np.random.rand(100, 100) 264 | xy = np.stack([x, y], axis=2) 265 | r, angle = polar(xy) 266 | xyn = cartesian(r, angle) 267 | pprint(xy - xyn) 268 | -------------------------------------------------------------------------------- /hallucinator/field.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | 3 | 4 | #TODO 2d and 3d? 5 | def slope_field(points, 6 | arrow_length = 1, 7 | arrow_head_length = 0.2): 8 | """ 9 | Draw an arrow with slope f'(x, y) at each point (x, y) in points 10 | :param points: (x, y, f'(x, y)) 11 | :return: 12 | """ 13 | field = hl.MonochromeScene() 14 | for point in points: 15 | field.add_object(hl.arrow(p0=point[0:2], 16 | direction=point[2], 17 | length=arrow_length, 18 | head_length=arrow_head_length), 19 | name='{0}{1}'.format(point[0], point[1])) 20 | 21 | return field 22 | -------------------------------------------------------------------------------- /hallucinator/group.py: -------------------------------------------------------------------------------- 1 | import hallucinator 2 | import numpy as np 3 | import copy 4 | 5 | 6 | class Group: 7 | def __init__(self, region_type='path', species='default'): 8 | self.components = [] 9 | self.region_type = region_type 10 | self.species = species 11 | 12 | def add_component(self, comp): 13 | self.components.append(comp) 14 | return comp 15 | 16 | def transform(self, transformation): 17 | new_group = Group() 18 | for component in self.components: 19 | new_component = copy.deepcopy(component) 20 | new_component.position = np.matmul(transformation, component.position) 21 | new_group.add_component(new_component) 22 | return new_group 23 | 24 | def rotate(self, theta, p=(0, 0)): 25 | return self.transform(hallucinator.rotate_about(theta, p)) 26 | 27 | def translate(self, tx=0, ty=0): 28 | return self.transform(hallucinator.translate(tx, ty)) 29 | 30 | def scale(self, sx=1, sy=1, p=(0, 0)): 31 | return self.transform(hallucinator.scale_about(sx, sy, p)) 32 | 33 | def shear(self, sx=0, sy=0, p=(0, 0)): 34 | return self.transform(hallucinator.shear_about(sx, sy, p)) 35 | 36 | def mirror(self, axis='x', offset=0): 37 | return self.transform(hallucinator.mirror_about(axis, offset)) 38 | 39 | def copy(self): 40 | return copy.deepcopy(self) 41 | 42 | 43 | class Group3(Group): 44 | def __init__(self, region_type='path', species='default'): 45 | Group.__init__(self, region_type, species) 46 | 47 | def transform(self, transformation): 48 | new_group = Group3() 49 | for component in self.components: 50 | new_component = copy.deepcopy(component) 51 | new_component.position = np.matmul(transformation, new_component.position) 52 | new_group.add_component(new_component) 53 | return new_group 54 | 55 | def rotate(self, theta, axis=(1, 0, 0), p=(0, 0, 0)): 56 | return self.transform(hallucinator.rotate_about_3(theta, axis, p)) 57 | 58 | def translate(self, tx=0, ty=0, tz=0): 59 | return self.transform(hallucinator.translate_3(tx, ty, tz)) 60 | 61 | def scale(self, sx=1, sy=1, sz=1, p=(0, 0, 0)): 62 | return self.transform(hallucinator.scale_about_3(sx, sy, sz, p)) 63 | 64 | def shear(self, xy=0, xz=0, yx=0, yz=0, zx=0, zy=0, p=(0, 0, 0)): 65 | return self.transform(hallucinator.shear_about_3(xy, xz, yx, yz, zx, zy, p)) 66 | 67 | def mirror(self, plane): 68 | print('not implemented') 69 | 70 | # TODO fix length 71 | def project(self, method='ortho', z_factor=0.02): 72 | new_group = Group(species=self.species + '_projected') 73 | for component in self.components: 74 | new_group.add_component(component.project(method=method, z_factor=z_factor)) 75 | return new_group 76 | -------------------------------------------------------------------------------- /hallucinator/integral.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import numpy as np 3 | 4 | 5 | def integrate_vectors(vectors): 6 | endpoint = np.array([0, 0]) 7 | new_endpoints = np.array([[0, 0]]) 8 | for v in vectors: 9 | endpoint = np.add(endpoint, v) 10 | new_endpoints = np.append(new_endpoints, [endpoint], axis=0) 11 | return new_endpoints 12 | 13 | 14 | def draw_path(vectors, endpoints, scene): 15 | for i, vector in enumerate(vectors): 16 | scene.add_object(hl.arrow(p0=endpoints[i], direction=vector, length=1, head_length=0.3), name=f"arrow{i}") 17 | 18 | 19 | # plots one dimensional phase integral (like clothoid) of complex pattern 20 | # currently samples along y=0 21 | # TODO arbitrary path 22 | def plot_phase_integral(pattern, plot_range, resolution, scene): 23 | num_samples = (plot_range[1] - plot_range[0]) * resolution 24 | sample_points = [(p, 0) for p in np.linspace(plot_range[0], plot_range[1], num_samples)] 25 | sampled_vectors = np.array([pattern(p) for p in sample_points]) 26 | endpoints = integrate_vectors(sampled_vectors) 27 | draw_path(sampled_vectors, endpoints, scene) 28 | return endpoints 29 | 30 | 31 | -------------------------------------------------------------------------------- /hallucinator/mortalsampler.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def path_points(path_range, path_length='auto', density=1): 5 | if path_length == 'auto': 6 | path_length = path_range[1] - path_range[0] 7 | num_points = int(round(path_length*density)) 8 | points = np.linspace(path_range[0], path_range[1], num=num_points) 9 | return points 10 | 11 | 12 | def surface_points(surface_range, length=('auto', 'auto'), density=(1, 1)): 13 | length = list(length) 14 | if length[0] == 'auto': 15 | length[0] = surface_range[0][1] - surface_range[0][0] 16 | if length[1] == 'auto': 17 | length[1] = surface_range[1][1] - surface_range[1][0] 18 | num_points_a = int(round(length[0] * density[0])) 19 | num_points_b = int(round(length[1] * density[1])) 20 | a_axis = np.linspace(surface_range[0][0], surface_range[0][1], num=num_points_a) 21 | b_axis = np.linspace(surface_range[1][0], surface_range[1][1], num=num_points_b) 22 | meshgrid = np.meshgrid(a_axis, b_axis) 23 | ab = np.stack(meshgrid, axis=2) 24 | return ab 25 | 26 | 27 | def eval_path(f, points): 28 | f = np.vectorize(f) 29 | return np.array(f(points)) 30 | 31 | 32 | def eval_surf(f, a_axis, b_axis): 33 | f = np.vectorize(f) 34 | pts = np.array(f(a_axis[:, None], b_axis[None, :])) 35 | pts = pts.reshape((pts.shape[0], pts.shape[1]*pts.shape[2])) 36 | return pts 37 | 38 | 39 | # TODO give params default value 40 | # TODO function for reused code 41 | def path_region(at, params, path_range, path_length="auto", 42 | p_name='p', 43 | density=1): 44 | """ 45 | :param at: f(p) -> (x, y, ( , gradient, or (R, G, B))) 46 | :param params: anything 47 | :param path_range: (path_i, path_f) 48 | :param path_length: geometric length of path 49 | :param p_name: name of parameter 50 | :param density: num points to evaluate per range unit 51 | :return: set of points (x, y, ( , gradient, or (R, G, B))) 52 | """ 53 | if path_length == 'auto': 54 | path_length = path_range[1] - path_range[0] 55 | points = set() 56 | for eval_at in np.linspace(path_range[0], path_range[1], num=int(round(path_length * density))): 57 | params[p_name] = eval_at 58 | points.add(at(params)) 59 | 60 | return points 61 | 62 | 63 | def surface_region(at, params, a_range, b_range, 64 | a_length='auto', 65 | b_length='auto', 66 | a_name='a', 67 | b_name='b', 68 | a_density=1, 69 | b_density=1): 70 | points = set() 71 | if a_length == 'auto': 72 | a_length = a_range[1] - a_range[0] 73 | if b_length == 'auto': 74 | b_length = b_range[1] - b_range[0] 75 | for a in np.linspace(a_range[0], a_range[1], a_length * a_density): 76 | for b in np.linspace(b_range[0], b_range[1], b_length * b_density): 77 | params[a_name] = a 78 | params[b_name] = b 79 | points.add(at(params)) 80 | 81 | # print(points) 82 | return points 83 | 84 | 85 | def surface_region_random(at, params, a_range, b_range, 86 | a_name='a', 87 | b_name='b', 88 | density=1): 89 | pass 90 | 91 | 92 | def wireframe(at, params, a_range, b_range, a_spacing=3, b_spacing=3, 93 | a_length='auto', b_length='auto', density=1, a_name='a', b_name='b'): 94 | points = set() 95 | if a_length == 'auto': 96 | a_length = a_range[1] - a_range[0] 97 | if b_length == 'auto': 98 | b_length = b_range[1] - b_range[0] 99 | for a in np.linspace(a_range[0], a_range[1], int(round(a_length / a_spacing))): 100 | for b in np.linspace(b_range[0], b_range[1], int(round(b_length * density))): 101 | params[a_name] = a 102 | params[b_name] = b 103 | points.add(at(params)) 104 | for b in np.linspace(b_range[0], b_range[1], int(round(b_length / b_spacing))): 105 | for a in np.linspace(a_range[0], a_range[1], int(round(a_length * density))): 106 | params[a_name] = a 107 | params[b_name] = b 108 | points.add(at(params)) 109 | return points 110 | 111 | 112 | def wireframe_lines(at, params, a_range, b_range, a_spacing=0.5, b_spacing=0.5, 113 | a_length='auto', b_length='auto', a_name='a', b_name='b', 114 | toggle_a=True, toggle_b=True): 115 | lines = set() 116 | if a_length == 'auto': 117 | a_length = a_range[1] - a_range[0] 118 | if b_length == 'auto': 119 | b_length = b_range[1] - b_range[0] 120 | if toggle_a: 121 | for a in np.linspace(a_range[0], a_range[1] - a_spacing, int(round(a_length / a_spacing))): 122 | for b in np.linspace(b_range[0], b_range[1], int(round(b_length / b_spacing))): 123 | params[a_name] = a 124 | params[b_name] = b 125 | p1 = at(params) 126 | params[a_name] = a + a_spacing 127 | p2 = at(params) 128 | lines.add((p1, p2)) 129 | # TODO need 2 loops? 130 | if toggle_b: 131 | for b in np.linspace(b_range[0], b_range[1] - b_spacing, int(round(b_length / b_spacing))): 132 | for a in np.linspace(a_range[0], a_range[1], int(round(a_length / a_spacing))): 133 | params[a_name] = a 134 | params[b_name] = b 135 | p1 = at(params) 136 | params[b_name] = b + b_spacing 137 | p2 = at(params) 138 | lines.add((p1, p2)) 139 | 140 | return lines 141 | 142 | 143 | def conditional_region(at, params, conditions, a_range, b_range, 144 | a_name='a', 145 | b_name='b', 146 | density=1): 147 | points = set() 148 | for a in np.linspace(a_range[0], a_range[1], (a_range[1] - a_range[0]) * density): 149 | for b in np.linspace(b_range[0], b_range[1], (b_range[1] - b_range[0]) * density): 150 | if all(condition(a, b) for condition in conditions): 151 | params[a_name] = a 152 | params[b_name] = b 153 | points.add(at(params)) 154 | 155 | return points 156 | -------------------------------------------------------------------------------- /hallucinator/obj.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import copy 3 | import math 4 | import numpy as np 5 | 6 | '''parametric functions''' 7 | 8 | 9 | # TODO change all return types to numpy arrays 10 | 11 | def line_parametric(p0, dx, dy): 12 | return lambda p: np.array([p0[0] + p * dx, p0[1] + p * dy]) 13 | 14 | 15 | def circle_parametric(r, c): 16 | return lambda p: np.array([r * math.cos(p) + c[0], r * math.sin(p) + c[1]]) 17 | 18 | 19 | '''object primitives''' 20 | 21 | 22 | # TODO do not write length in stone, but deal with this later 23 | def vector(p1, p2): 24 | x1, y1 = p1 25 | x2, y2 = p2 26 | x_len = x2 - x1 27 | y_len = y2 - y1 28 | distance = math.hypot(x_len, y_len) 29 | return hl.ParaObject2(line_parametric(p1, x_len / distance, y_len / distance), 30 | region_params={'path_range': (0, distance), 31 | 'path_length': distance}, 32 | species='vector') 33 | 34 | 35 | def circle_primitive(r, c): 36 | return hl.ParaObject2(circle_parametric(r, c), 37 | region_params={'path_range': (0, 2 * math.pi), 38 | 'path_length': 2 * math.pi * r}, 39 | species='circle') 40 | 41 | 42 | def ellipse(): 43 | pass 44 | 45 | 46 | def path(path_func, p_range, path_length="auto"): 47 | return hl.ParaObject2(path_func, 48 | region_params={'path_range': p_range, 49 | 'path_length': path_length}, 50 | species="path") 51 | 52 | ''' 53 | # TODO polarization varies with p 54 | # TODO start time 55 | def disturbance_on_path(disturbance, init_pos, polarization, path, p_range, path_length="auto"): 56 | """ 57 | :param disturbance: 58 | :param v: 59 | :param init_pos: 60 | :param polarization: 61 | :param path: 62 | :param p_range: 63 | :param path_length: 64 | :return: 65 | """ 66 | 67 | def f(p, t): return tuple(np.add((disturbance(p - init_pos, t) * np.asarray(polarization)), path(p))) 68 | 69 | return hl.ParaObject2(f, 70 | region_params={'path_range': p_range, 71 | 'path_length': path_length}, 72 | species='disturbance_on_path')''' 73 | 74 | 75 | def textured_path(texture, pos, polarization, path, p_range, path_length): 76 | def f(p): return tuple(np.add((texture(p - pos) * np.asarray(polarization)), path(p))) 77 | 78 | return hl.ParaObject2(f, 79 | region_params={'path_range': p_range, 80 | 'path_length': path_length}, 81 | species='textured_path') 82 | 83 | 84 | '''groups''' 85 | 86 | 87 | # TODO with transforms instead 88 | # TODO remove p0? 89 | def rectangle(h=10, w=10, p0=(0, 0)): 90 | rect = hl.Group(species='rectangle') 91 | rect.add_component(vector((p0[0], p0[1]), (p0[0], p0[1] + h))) 92 | rect.add_component(vector((p0[0], p0[1]), (p0[0] + w, p0[1]))) 93 | rect.add_component(vector((p0[0] + w, p0[1]), (p0[0] + w, p0[1] + h))) 94 | rect.add_component(vector((p0[0], p0[1] + h), (p0[0] + w, p0[1] + h))) 95 | return rect 96 | 97 | 98 | def square(w, p0): 99 | return rectangle(w, w, p0) 100 | 101 | 102 | def polygon(w, n): 103 | poly = hl.Group(species='{0}_gon'.format(n)) 104 | side = vector((0, 0), (w, 0)) 105 | pivot = 1 106 | angle = (n - 2) * math.pi / n 107 | for i in range(n): 108 | poly.add_component(copy.deepcopy(side)) 109 | end = side.eval_at(w if pivot else 0)[0:2] 110 | side = side.rotate(theta=-angle, p=end) 111 | pivot = not pivot 112 | return poly 113 | 114 | 115 | def wheel(radius, num_spokes): 116 | w = hl.Group(species='wheel') 117 | for i in range(num_spokes): 118 | spoke = vector((0, 0), (radius, 0)) 119 | spoke = spoke.rotate(theta=i*2*math.pi/num_spokes) 120 | w.add_component(spoke) 121 | w.add_component(circle_primitive(r=radius, c=(0, 0))) 122 | return w 123 | 124 | 125 | def axes(x_range, y_range, origin=(0, 0)): 126 | ax = hl.Group(species='axes') 127 | ax.add_component(vector((origin[0] + x_range[0], origin[1]), (origin[0] + x_range[1], origin[1]))) 128 | ax.add_component(vector((origin[0], origin[1] + y_range[0]), (origin[0], origin[1] + y_range[1]))) 129 | return ax 130 | 131 | 132 | def arrow(p0, direction, length=None, head_length=0, centered=False): 133 | arw = hl.Group(species='arrow') 134 | length = np.linalg.norm(direction[0] - direction[1], axis=1) if not length else length 135 | direction = np.array(direction, copy=False, dtype=float) 136 | direction /= np.linalg.norm(direction) 137 | path_range = (-length / 2, length / 2) if centered else (0, length) 138 | arw.add_component(hl.ParaObject2(line_parametric(p0, direction[0], direction[1]), 139 | region_params={'path_range': path_range, 140 | 'path_length': length}, 141 | species='arrow_body')) 142 | if not head_length == 0: 143 | arrow_tip_coordinates = np.add(p0, np.asarray(direction) * (length / 2)) 144 | arrowhead_dir_1 = np.matmul(hl.rotate(3 * math.pi / 4)[:2, :2], direction) 145 | arrowhead_dir_2 = np.matmul(hl.rotate(-3 * math.pi / 4)[:2, :2], direction) 146 | arw.add_component(hl.ParaObject2(line_parametric(arrow_tip_coordinates, arrowhead_dir_1[0], arrowhead_dir_1[1]), 147 | region_params={'path_range': (0, head_length), 148 | 'path_length': head_length}, 149 | species='arrow_head')) 150 | 151 | arw.add_component(hl.ParaObject2(line_parametric(arrow_tip_coordinates, arrowhead_dir_2[0], arrowhead_dir_2[1]), 152 | region_params={'path_range': (0, head_length), 153 | 'path_length': head_length}, 154 | species='arrow_head')) 155 | return arw 156 | -------------------------------------------------------------------------------- /hallucinator/optics.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | from pathos.multiprocessing import ProcessingPool as Pool 4 | 5 | import hallucinator as hl 6 | import numpy as np 7 | import random 8 | 9 | # TODO integrate 10 | # TODO make 3d 11 | 12 | C = 3 * 10 ** 8 13 | 14 | """color frequencies""" 15 | RED_F = 430 * 10 ** 12 16 | ORANGE_F = 480 * 10 ** 12 17 | YELLOW_F = 510 * 10 ** 12 18 | GREEN_F = 540 * 10 ** 12 19 | CYAN_F = 580 * 10 ** 12 20 | BLUE_F = 610 * 10 ** 12 21 | VIOLET_F = 670 * 10 ** 12 22 | 23 | 24 | def distance(p1, p2): 25 | return np.linalg.norm(p1 - p2) 26 | 27 | 28 | def polar_to_cart(polar): 29 | x = polar[0] * np.cos(polar[1]) 30 | y = polar[0] * np.sin(polar[1]) 31 | return x, y 32 | 33 | 34 | def cart_to_polar(cart): 35 | amplitude = np.sqrt(cart[0] ** 2 + cart[1] ** 2) 36 | phase = np.arctan2(cart[1], cart[0]) 37 | return amplitude, phase 38 | 39 | 40 | def real_field_at(sources, location): 41 | return field_at(sources, location)[0] 42 | 43 | 44 | def field_at(sources, location): 45 | """returns field vector after interference of sources 46 | at location in cartesian coordinates""" 47 | vector = polar_to_cart(sources[0].detect(location)) 48 | for source in sources[1:]: 49 | vector = np.add(vector, polar_to_cart(source.detect(location))) 50 | return vector 51 | 52 | 53 | def intensity_at(sources, location): 54 | vector = field_at(sources, location) 55 | return vector[0] ** 2 + vector[1] ** 2 56 | 57 | 58 | def eval_surface(sources, surface, a_range, b_range, 59 | a_length='auto', 60 | b_length='auto', 61 | mode = 'grid', 62 | func=intensity_at, 63 | a_density=1, 64 | b_density=1): 65 | points = set() 66 | if a_length == 'auto': 67 | a_length = a_range[1] - a_range[0] 68 | if b_length == 'auto': 69 | b_length = b_range[1] - b_range[0] 70 | 71 | a_coords = np.linspace(a_range[0], a_range[1], a_length * a_density) 72 | b_coords = np.linspace(b_range[0], b_range[1], b_length * b_density) 73 | eval_points = list(itertools.product(a_coords, b_coords)) 74 | 75 | def eval_random_point(_): 76 | a = random.uniform(a_range[0], a_range[1]) 77 | b = random.uniform(b_range[0], b_range[1]) 78 | return a, b, func(sources, surface(a, b)) 79 | 80 | 81 | with Pool() as pool: 82 | if mode == 'random': 83 | num_points = math.ceil(a_length * b_length * a_density ** 2) 84 | points = pool.map(eval_random_point, [0] * num_points) 85 | else: 86 | a_coords = np.linspace(a_range[0], a_range[1], a_length * a_density) 87 | b_coords = np.linspace(b_range[0], b_range[1], b_length * b_density) 88 | eval_points = list(itertools.product(a_coords, b_coords)) 89 | points = pool.map(lambda p: (p[0], p[1], intensity_at(sources, surface(p[0], p[1]))), eval_points) 90 | 91 | return points 92 | 93 | 94 | '''def eval_surface(sources, surface, a_range, b_range, 95 | a_length='auto', 96 | b_length='auto', 97 | a_density=1, 98 | b_density=1): 99 | points = set() 100 | if a_length == 'auto': 101 | a_length = a_range[1] - a_range[0] 102 | if b_length == 'auto': 103 | b_length = b_range[1] - b_range[0] 104 | 105 | a_coords = np.linspace(a_range[0], a_range[1], a_length * a_density) 106 | b_coords = np.linspace(b_range[0], b_range[1], b_length * b_density) 107 | eval_points = list(itertools.product(a_coords, b_coords)) 108 | 109 | with Pool() as pool: 110 | points = pool.map(lambda p: (p[0], p[1], intensity_at(sources, surface(p[0], p[1]))), eval_points) 111 | 112 | # for a in np.linspace(a_range[0], a_range[1], a_length * a_density): 113 | # for b in np.linspace(b_range[0], b_range[1], b_length * b_density): 114 | # points.add((a, b, intensity_at(sources, surface(a, b)))) 115 | return points''' 116 | 117 | 118 | # TODO polarization 119 | class Light: 120 | def __init__(self, source=(0, 0), frequency=RED_F, amplitude=1, phase_offset=0, velocity=C): 121 | """ 122 | :param source: location (x, y) of source 123 | :param frequency: temporal frequency 124 | :param phase_offset: phase offset in radians (0 - 2*pi) 125 | :param velocity: propagation speed 126 | """ 127 | self.source = np.asarray(source) 128 | self.phase_offset = phase_offset 129 | self.frequency = frequency 130 | self.amplitude = amplitude 131 | self.v = velocity 132 | self.wavelength = self.v / self.frequency 133 | self.k = 2 * math.pi / self.wavelength 134 | 135 | def change_frequency(self, new_frequency): 136 | self.frequency = new_frequency 137 | self.wavelength = self.v / self.frequency 138 | self.k = 2 * math.pi / self.wavelength 139 | 140 | def change_wavelength(self, new_wavelength): 141 | self.wavelength = new_wavelength 142 | self.frequency = self.v / self.wavelength 143 | self.k = 2 * math.pi / self.wavelength 144 | 145 | def phase_at(self, location): 146 | """ 147 | returns phase angle (0 - 2 * pi) 148 | """ 149 | r = self.path_length(location) 150 | return (self.k * r + self.phase_offset) % (2 * math.pi) 151 | 152 | def path_length(self, location): 153 | raise Exception("Not implemented") 154 | 155 | def detect(self, location): 156 | # todo scale by path length 157 | return self.amplitude, self.phase_at(location) 158 | 159 | 160 | class PointSource(Light): 161 | def __init__(self, source=(0, 0), frequency=RED_F, amplitude=1, phase_offset=0, velocity=C): 162 | Light.__init__(self, source, frequency, amplitude, phase_offset, velocity) 163 | 164 | def path_length(self, location): 165 | return distance(self.source, location) 166 | 167 | def detect(self, location): 168 | return self.amplitude / self.path_length(location), self.phase_at(location) 169 | 170 | 171 | class PlaneWave(Light): 172 | def __init__(self, source=(0, 0), frequency=RED_F, amplitude=1, phase_offset=0, direction=(1, 0), velocity=C): 173 | Light.__init__(self, source, frequency, amplitude, phase_offset, velocity) 174 | self.direction = np.asarray(direction) 175 | 176 | def path_length(self, location): 177 | return np.abs(np.dot(np.asarray(location) - self.source, self.direction) / np.linalg.norm(self.direction)) 178 | 179 | 180 | '''def intensity(sources, point): 181 | #check coherence 182 | if not sources[0].wavelength == sources[1].wavelength: 183 | print('incoherent') 184 | return 185 | init_phase_diff = sources[1].init_phase - sources[0].init_phase 186 | path_difference = sources[1].path_length(point) - sources[0].path_length(point) 187 | path_phase_diff = 2 * math.pi * path_difference / sources[1].wavelength 188 | phase_diff = init_phase_diff + path_phase_diff 189 | a = sources[0].amplitude 190 | b = sources[1].amplitude 191 | combined_amplitude = math.sqrt(a**2 + b**2 + 2*a*b*math.cos(phase_diff)) 192 | return combined_amplitude**2''' 193 | -------------------------------------------------------------------------------- /hallucinator/paraobj.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import numpy as np 3 | import copy 4 | 5 | 6 | class ParaObject: 7 | def __init__(self, f, region_type="path", region_params="none", species='default'): 8 | self.f = f 9 | self.region_type = region_type 10 | if region_params == "none": 11 | self.region_params = {} 12 | else: 13 | self.region_params = region_params 14 | self.species = species 15 | self.position = None 16 | 17 | def eval_at(self, p): 18 | return self.at({'p': p}) 19 | 20 | def at(self, **params): 21 | """ 22 | :param params: 23 | :return: (x, y, ( , gradient, or (R, G, B))) 24 | """ 25 | there = self.f(**params) 26 | values = None 27 | if len(there) > self.position.shape[1] - 1: 28 | num_vals = len(there) + 1 - self.position.shape[0] 29 | values = there[-num_vals] 30 | there = there[:-num_vals] 31 | unnormalized_coordinates = np.matmul(self.position, there + (1,)) 32 | normalized_coordinates = unnormalized_coordinates / unnormalized_coordinates[-1] 33 | # transformed = tuple(normalized_coordinates[:-1]) + (there[-1],) 34 | 35 | transformed = tuple(normalized_coordinates[:-1]) 36 | return transformed + (values,) 37 | 38 | def transform(self, transformation): 39 | new_component = self.copy() 40 | new_component.position = np.matmul(transformation, new_component.position) 41 | return new_component 42 | 43 | # TODO does everything work right? 44 | def copy(self): 45 | return copy.deepcopy(self) 46 | 47 | def region(self, density): 48 | if self.region_type == 'path': 49 | sampler = hl.path_points(**self.region_params, density=density) 50 | elif self.region_type == '2d': 51 | sampler = hl.surface_points(**self.region_params, density=density) 52 | elif self.region_type == 'lattice': 53 | print('lattice') 54 | return 55 | else: 56 | print('error: invalid region type') 57 | return 58 | # print('sampler:', sampler) 59 | # print(type(sampler)) 60 | # print('f sampler:', self.f(sampler)) 61 | # print(type(self.f(sampler))) 62 | # print(self.f) 63 | return self.f(sampler) 64 | 65 | 66 | class ParaObject2(ParaObject): 67 | def __init__(self, f, region_type="path", region_params="none", species='default'): 68 | ParaObject.__init__(self, f, region_type, region_params, species) 69 | self.position = hl.IDENTITY3 70 | 71 | def rotate(self, theta, p=(0, 0)): 72 | return self.transform(hl.rotate_about(theta, p)) 73 | 74 | def translate(self, tx=0, ty=0): 75 | return self.transform(hl.translate(tx, ty)) 76 | 77 | def scale(self, sx=1, sy=1, p=(0, 0)): 78 | return self.transform(hl.scale_about(sx, sy, p)) 79 | 80 | def shear(self, sx=0, sy=0, p=(0, 0)): 81 | return self.transform(hl.shear_about(sx, sy, p)) 82 | 83 | def mirror(self, axis='x', offset=0): 84 | return self.transform(hl.mirror_about(axis, offset)) 85 | 86 | def add_disturbance(self, disturbance, init_pos, polarization): 87 | def f(p, t): return disturbance(p - init_pos, t) * np.asarray(polarization) 88 | 89 | self.f = lambda p, t: tuple(np.add(self.f(p, t), f(p, t))) 90 | 91 | 92 | class ParaObject3(ParaObject): 93 | def __init__(self, f, region_type="path", region_params="none", species='default'): 94 | ParaObject.__init__(self, f, region_type, region_params, species) 95 | self.position = hl.IDENTITY4 96 | 97 | def project(self, method='ortho', z_factor=0.02): 98 | new = ParaObject2(f=self.f, 99 | region_type=self.region_type, 100 | region_params=self.region_params, 101 | species=self.species + '_projected') 102 | 103 | if method == 'ortho': 104 | projection_matrix = hl.ORTHO_PROJECT 105 | elif method == 'weak': 106 | projection_matrix = hl.weak_project(z_factor) 107 | else: 108 | projection_matrix = hl.ORTHO_PROJECT 109 | 110 | new_position = np.matmul(projection_matrix, self.position) 111 | new.position = np.delete(new_position, 2, axis=0) 112 | return new 113 | 114 | def rotate(self, theta, axis=(1, 0, 0), p=(0, 0, 0)): 115 | return self.transform(hl.rotate_about_3(theta, axis, p)) 116 | 117 | def translate(self, t): 118 | return self.transform(hl.translate_3(t)) 119 | 120 | def scale(self, sx=1, sy=1, sz=1, p=(0, 0, 0)): 121 | return self.transform(hl.scale_about_3(sx, sy, sz, p)) 122 | 123 | def shear(self, xy=0, xz=0, yx=0, yz=0, zx=0, zy=0, p=(0, 0, 0)): 124 | return self.transform(hl.shear_about_3(xy, xz, yx, yz, zx, zy, p)) 125 | 126 | def mirror(self, plane): 127 | print('not implemented') 128 | 129 | # TODO only good for surfaces 130 | # TODO combine these two functions into one 131 | # only works if there is not already a disturbance 132 | def add_disturbance(self, disturbance, init_pos, polarization, start_time=0): 133 | def f(a, b, t): return 0 if t - start_time < 0 else (disturbance(a - init_pos[0], 134 | b - init_pos[1], 135 | t - start_time) 136 | * np.asarray(polarization)) 137 | 138 | copy_of_f = copy.copy(self.f) 139 | self.f = lambda a, b, t: tuple(np.add(f(a, b, t), copy_of_f(a, b))) 140 | 141 | # only works if there is already a time dependent disturbance 142 | def add_more_disturbances(self, disturbance, init_pos, polarization, start_time=0): 143 | def f(a, b, t): return 0 if t - start_time < 0 else (disturbance(a - init_pos[0], b - init_pos[1], t-start_time) 144 | * np.asarray(polarization)) 145 | 146 | copy_of_f = copy.copy(self.f) 147 | self.f = lambda a, b, t: tuple(np.add(f(a, b, t), copy_of_f(a, b, t))) 148 | -------------------------------------------------------------------------------- /hallucinator/perspective.py: -------------------------------------------------------------------------------- 1 | import copy 2 | from enum import Enum 3 | 4 | import numpy as np 5 | import numexpr as ne 6 | import math 7 | import hallucinator as hl 8 | 9 | 10 | def sample_plane(x_range, y_range, f): 11 | x_size = x_range[1] - x_range[0] 12 | y_size = y_range[1] - y_range[0] 13 | plane = np.empty([x_size, y_size]) 14 | for a in range(x_size): 15 | for b in range(y_size): 16 | plane[a][b] = f(x_range[0]+a, y_range[0]+b) 17 | 18 | return plane 19 | 20 | 21 | def f(p): 22 | return math.sin(p[0]**2 + p[1]**2) 23 | 24 | 25 | def phase(distance, wavelength): 26 | return distance * 2 * math.pi / wavelength 27 | 28 | 29 | def dist(p1, p2): 30 | return np.linalg.norm(p1-p2) 31 | 32 | 33 | def parabola(p): 34 | return 0.1*(p[0]**2 + p[1]**2) 35 | 36 | 37 | def slope(p): 38 | return p[0] 39 | 40 | 41 | def sloped_parabola(p): 42 | return slope(p) + parabola(p) 43 | 44 | 45 | def perspective_transform(coordinates, wavelength, f, surface=lambda p: 0): 46 | def perspective(p): 47 | distance = dist(coordinates, np.array([*p, surface(p)])) 48 | phase_change = phase(distance, wavelength) 49 | return math.sin(f(p) + phase_change) 50 | return perspective 51 | 52 | 53 | def depth_map(wavelength, surface=lambda p: 0): 54 | def depth(p): 55 | distance = surface(p) 56 | return math.sin(phase(distance, wavelength)) 57 | 58 | return depth 59 | 60 | 61 | ################################################################## 62 | # Strange code 63 | ################################################################# 64 | 65 | 66 | # Takes a plane of x-y coordinates at z=0 and returns an array of path-lengths from the point p 67 | # shape (n, m, 2) -> (n, m) 68 | def perspective_plane(xy, p=(0, 0, 10)): 69 | pxy = p[:-1] 70 | x2y2 = ne.evaluate("sum((xy-pxy)**2, axis=2)") 71 | z2 = p[-1]**2 72 | return ne.evaluate("sqrt(x2y2+z2)") 73 | 74 | 75 | # Is this right? 76 | def fourier_plane(xy, p=(0, 0)): 77 | return perspective_plane(xy, p=(*p[:2], 0)) 78 | 79 | 80 | # Turns a perspective plane into a zone plate with the given wavelength 81 | # shape (n, m) -> (n, m) 82 | def perspective_zp(persp_plane, wavelength=0.01): 83 | frequency = 2 * math.pi / wavelength 84 | return ne.evaluate("sin(persp_plane*frequency)") 85 | 86 | 87 | def opl_zp(persp_plane, wavelength=0.01): 88 | frequency = 2 * math.pi / wavelength 89 | return ne.evaluate("persp_plane*frequency") 90 | 91 | 92 | def phase_threshold(values, threshold=2*math.pi): 93 | return ne.evaluate("values % threshold") 94 | 95 | 96 | def real(values): 97 | return ne.evaluate("cos(values)") 98 | 99 | 100 | def imaginary(values): 101 | return ne.evaluate("sin(values)") 102 | 103 | 104 | def phase_conjugate(values, threshold=2*math.pi): 105 | return ne.evaluate("threshold - values") 106 | 107 | 108 | '''xy = xy_plane() 109 | persp_xy = perspective_plane(xy) 110 | zp = perspective_zp(persp_xy) 111 | hl.render_from_array(imagify(xy)) 112 | hl.render_from_array(imagify(persp_xy)) 113 | hl.render_from_array(imagify(zp))''' 114 | 115 | # hl.video2( 116 | # frame_function=lambda z: imagify(perspective_zp(x2y2, z)), 117 | # frame_arguments=np.geomspace(1, 1000, num=200), 118 | # fps=10, 119 | # preview=False, 120 | # filename="../images/perspective_zone" 121 | # ) 122 | 123 | 124 | ################################################################## 125 | # Demos 126 | ################################################################## 127 | 128 | 129 | # 130 | # plane = hl.sampling_image(f, value_range=(-10, 10), image_size=(1000, 1000), binary=False) 131 | # hl.render_from_array(plane) 132 | # 133 | # zp = perspective_transform(np.array([0, 0, 10]), 0.01, lambda p: 1) 134 | # plane = hl.sampling_image(zp, value_range=(-10, 10), image_size=(500, 500), binary=False) 135 | # hl.render_from_array(plane) 136 | # 137 | # par = depth_map(wavelength=1, surface=sloped_parabola) 138 | # plane = hl.sampling_image(par, value_range=(-10, 10), image_size=(1500, 1500), 139 | # binary=True, parallel_sample=True) 140 | # hl.render_from_array(plane) 141 | # 142 | # persp = perspective_transform(np.array([0, 0, 10]), 0.01, zp) 143 | # plane2 = hl.sampling_image(persp, value_range=(-3, 3), image_size=(1500, 1500), parallel_sample=False) 144 | # print(np.sum(plane2/127.5-1)) 145 | # hl.render_from_array(plane2) 146 | # 147 | # 148 | # persp = perspective_transform(np.array([1, 0, 10]), 0.01, zp) 149 | # plane2 = hl.sampling_image(persp, value_range=(-3, 3), image_size=(1500, 1500), parallel_sample=False) 150 | # print(np.sum(plane2/127.5-1)) 151 | # hl.render_from_array(plane2) 152 | # 153 | # persp = perspective_transform(np.array([10, 0, 10]), 0.01, zp) 154 | # plane2 = hl.sampling_image(persp, value_range=(-3, 3), image_size=(1500, 1500), parallel_sample=False) 155 | # print(np.sum(plane2/127.5-1)) 156 | # hl.render_from_array(plane2) 157 | # 158 | # persp = perspective_transform(np.array([0, 0, 15]), 0.01, zp) 159 | # plane2 = hl.sampling_image(persp, value_range=(-3, 3), image_size=(1500, 1500), parallel_sample=False) 160 | # print(np.sum(plane2/127.5-1)) 161 | # hl.render_from_array(plane2) 162 | # 163 | # persp = perspective_transform(np.array([1, 0, 15]), 0.01, zp) 164 | # plane2 = hl.sampling_image(persp, value_range=(-3, 3), image_size=(1500, 1500), parallel_sample=False) 165 | # print(np.sum(plane2/127.5-1)) 166 | # hl.render_from_array(plane2) 167 | # 168 | # persp = perspective_transform(np.array([10, 0, 15]), 0.01, zp) 169 | # plane2 = hl.sampling_image(persp, value_range=(-3, 3), image_size=(1500, 1500), parallel_sample=False) 170 | # print(np.sum(plane2/127.5-1)) 171 | # hl.render_from_array(plane2) 172 | 173 | 174 | 175 | 176 | 177 | ################################################################## 178 | # This is too cute. They need to collapse into numpy arrays too quickly. 179 | ################################################################## 180 | 181 | # class Zoneplate: 182 | # 183 | # class types(Enum): 184 | # FRESNEL = "Fresnel" 185 | # FOURIER = "Fourier" 186 | # LINEAR = "Linear" 187 | # EXPONENTIAL = "Exponential" 188 | # CUSTOM = "Custom" 189 | # 190 | # def __init__(self, 191 | # type=types.FRESNEL, 192 | # wavelength=0.01, 193 | # center=(0, 0, 10), 194 | # value_range=(-1, 1), 195 | # resolution=(-1, 1) 196 | # ): 197 | # 198 | # self.type = type 199 | # self.wavelength = wavelength 200 | # self.center = center 201 | # self.value_range = value_range 202 | # self.resolution = resolution 203 | # self._evaluated = None 204 | # 205 | # def evaluate_fresnel(self): 206 | # xy = hl.xy_plane(value_range=self.value_range, resolution=self.resolution) 207 | # persp_plane = hl.perspective_plane(p=self.center, xy=xy) 208 | # return hl.phase_threshold(hl.opl_zp(persp_plane, wavelength=self.wavelength)) 209 | # 210 | # def evaluate(self): 211 | # if self.type == Zoneplate.types.FRESNEL: 212 | # return self.evaluate_fresnel() 213 | # else: 214 | # raise NotImplementedError(f"{self.type} zone plate not implemented") 215 | # 216 | # # Lazy property, evaluate the zoneplate on a plane 217 | # @property 218 | # def evaluated(self): 219 | # if self._evaluated is None: 220 | # self._evaluated = self.evaluate() 221 | # return self._evaluated 222 | # 223 | # def __add__(self, other): 224 | # if isinstance(other, Zoneplate): 225 | # other = other.evaluated 226 | # return hl.phase_threshold(self.evaluated + other) 227 | # 228 | # def __radd__(self, other): 229 | # if isinstance(other, Zoneplate): 230 | # other = other.evaluated 231 | # return hl.phase_threshold(self.evaluated + other) 232 | # 233 | # def __sub__(self, other): 234 | # print(type(other.evaluated), type(self.evaluated)) 235 | # return hl.phase_threshold(self.evaluated - other.evaluated) 236 | # 237 | # def __rsub__(self, other): 238 | # if isinstance(other, Zoneplate): 239 | # other = other.evaluated 240 | # return hl.phase_threshold(self.evaluated - other) 241 | # 242 | # def __invert__(self): 243 | # new = copy.copy() 244 | # new.type = self.type 245 | # new._evaluated = hl.phase_conjugate(self.evaluated) 246 | # return new 247 | # 248 | # def __repr__(self): 249 | # return f"Zp( type={self.type}" 250 | 251 | 252 | -------------------------------------------------------------------------------- /hallucinator/plotting.py: -------------------------------------------------------------------------------- 1 | import math 2 | from matplotlib import cm 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import matplotlib.gridspec as gridspec 6 | from matplotlib.widgets import Slider 7 | import numexpr as ne 8 | 9 | 10 | from hallucinator import xy_plane, perspective_zp, perspective_plane, fourier_plane, hl 11 | 12 | 13 | def plot_images(images, titles=None): 14 | image_plots = create_plots_grid(num_plots=len(images), titles=titles) 15 | for i in range(len(images)): 16 | image_plots[i].imshow(images[i], cmap=cm.gray, aspect="auto") 17 | plt.show() 18 | 19 | 20 | def create_plots_grid(num_plots, titles=None, plot_cell=None): 21 | if num_plots % 4 == 0 or num_plots > 9: 22 | rows = max(1, math.ceil(num_plots / 4)) 23 | cols = 4 24 | else: 25 | rows = max(1, math.ceil(num_plots / 3)) 26 | cols = min(num_plots, 3) 27 | 28 | plot_cell = plot_cell if plot_cell else gridspec.GridSpec(1, 1)[0] 29 | hspace = 0.2 if titles else 0.01 30 | plot_grid = gridspec.GridSpecFromSubplotSpec(rows, cols, plot_cell, wspace=0.01, hspace=hspace) 31 | image_plots = [] 32 | for i in range(num_plots): 33 | ax = plt.subplot(plot_grid[i]) 34 | ax.axis("off") 35 | if titles and len(titles) > i: 36 | ax.set_title(titles[i]) 37 | image_plots.append(ax) 38 | 39 | return image_plots 40 | 41 | 42 | # Slider_params = ["param_name", min, max] 43 | # Caller must set slider.on_change(update_func) for each slider themselves 44 | def create_slider_plots(*, controls_cell, slider_params): 45 | num_sliders = len(slider_params) 46 | slider_grid = gridspec.GridSpecFromSubplotSpec(num_sliders, 1, controls_cell, wspace=0.01, hspace=1) 47 | sliders = [] 48 | for i, params in enumerate(slider_params): 49 | slider_ax = plt.subplot(slider_grid[i]) 50 | 51 | slider = Slider(slider_ax, 52 | label=params[0], 53 | valmin=params[1], 54 | valmax=params[2], 55 | valinit=(params[2]+params[1])/2) 56 | sliders.append(slider) 57 | 58 | return sliders 59 | 60 | 61 | # def create_multiplot(num_images=1, controls=False): 62 | # fig = plt.figure() 63 | # if controls: 64 | # rows = 2 65 | # parent_grid = gridspec.GridSpec(2, 1, wspace=0.025, hspace=0.05, left=0.05, bottom=0.05, right=0.95, top=0.95, 66 | # height_ratios=[10, 1]) 67 | # controls_cell = parent_grid[1] 68 | # else: 69 | # parent_grid = gridspec.GridSpec(1, 1, wspace=0.025, hspace=0.05, left=0.05, bottom=0.05, right=0.95, top=0.95) 70 | # plots_cell = parent_grid[0] 71 | # 72 | # return create_image_plots(plots_cell, num_images) 73 | 74 | 75 | def create_interactive_plot(*, images_func, num_images, slider_params, titles=None, cmap=cm.gray): 76 | fig = plt.figure() 77 | 78 | spacing = dict(wspace=0.025, hspace=0.05, left=0.05, bottom=0.05, right=0.95, top=0.95) 79 | parent_grid = gridspec.GridSpec(2, 1, **spacing, height_ratios=[10, 1]) 80 | image_plots = create_plots_grid(num_plots=num_images, titles=titles, plot_cell=parent_grid[0]) 81 | sliders = create_slider_plots(controls_cell=parent_grid[1], slider_params=slider_params) 82 | 83 | def update_func(val): 84 | slider_vals = {slider_param[0]: slider.val for slider_param, slider in zip(slider_params, sliders)} 85 | images = images_func(**slider_vals) 86 | for image, image_plot in zip(images, image_plots): 87 | image_plot.imshow(image, cmap=cmap, aspect="auto") 88 | for slider in sliders: 89 | slider.on_changed(update_func) 90 | 91 | # Render for the first time 92 | update_func(0) 93 | plt.show() 94 | 95 | 96 | ############################################################################## 97 | # TESTS 98 | ############################################################################## 99 | 100 | def test_create_plots_grid(num_images=9): 101 | def squiggle_xy(a, b, c, d): 102 | i = np.arange(0.0, 2*np.pi+0.05, 0.05) 103 | return np.sin(i*a)*np.cos(i*b), np.sin(i*c)*np.cos(i*d) 104 | 105 | plots = create_plots_grid(num_images) 106 | for i, ax in enumerate(plots): 107 | a = i // 4 + 1 108 | b = i % 4 + 1 109 | c, d = 2, 3 110 | ax.plot(*squiggle_xy(a, b, c, d)) 111 | 112 | plt.show() 113 | 114 | 115 | def test_plot_images(): 116 | def example_image(i): 117 | xy = xy_plane(value_range=(-10, 10), resolution=100) 118 | persp_xy = perspective_plane(xy, p=[0, 0, i*10]) 119 | zp = perspective_zp(persp_xy) 120 | return zp 121 | 122 | num_images = 6 123 | plot_images(images=[example_image(i) for i in range(num_images)]), 124 | # titles=[f"title {i}" for i in range(num_images)]) 125 | 126 | 127 | def test_create_interactive_plot(): 128 | xy = xy_plane(value_range=(-10, 10), resolution=300) 129 | example_params = dict( 130 | images_func=lambda x, y, z: [ 131 | perspective_zp(perspective_plane(xy, p=[x, y, z])), 132 | perspective_zp(perspective_plane(xy, p=[0, 0, 50])), 133 | perspective_zp(perspective_plane(xy, p=[x, y, z]) - perspective_plane(xy, p=[0, 0, -50])) 134 | ], 135 | num_images=3, 136 | titles=[ 137 | "zp: x,y,z", 138 | "zp: 0,0,-50", 139 | "combined" 140 | ], 141 | slider_params=[ 142 | ["x", -10, 10], 143 | ["y", -10, 10], 144 | ["z", 0, 100] 145 | ] 146 | ) 147 | create_interactive_plot(**example_params) 148 | 149 | 150 | def plot_zone_pinch(): 151 | def example_image(i): 152 | xy = xy_plane(value_range=(-10, 10), resolution=100) 153 | center = (0, 0) 154 | x2y2 = ne.evaluate("(xy-center)**2") 155 | x2 = x2y2[:, :, 0] 156 | y2 = x2y2[:, :, 1] 157 | x2my2 = ne.evaluate("x2 - y2") 158 | print(hl.np.max(x2my2)) 159 | return x2my2 160 | 161 | num_images = 1 162 | plot_images(images=[example_image(i) for i in range(num_images)]), 163 | 164 | 165 | if __name__ == "__main__": 166 | test_create_plots_grid() 167 | plot_zone_pinch() 168 | test_create_interactive_plot() 169 | 170 | 171 | -------------------------------------------------------------------------------- /hallucinator/timereversal.py: -------------------------------------------------------------------------------- 1 | import numexpr as ne 2 | import hallucinator as hl 3 | import numpy as np 4 | import math 5 | 6 | 7 | # make more general and move 8 | def field(sources, t=0, value_range=(-10, 10), resolution=16): 9 | source_planes = [] 10 | xy = hl.xy_plane(value_range=value_range, resolution=resolution) 11 | 12 | for s in sources: 13 | source_planes.append(source(xy=xy, t=t, **s)) 14 | source_planes = np.array(source_planes) 15 | total = ne.evaluate("sum(source_planes, axis=2)") 16 | return total, source_planes 17 | 18 | 19 | def polar_field(comp_field): 20 | real_field = comp_field[0] 21 | imag_field = comp_field[1] 22 | p_2 = math.pi / 2 23 | theta = ne.evaluate("arctan2(real_field, imag_field) + p_2 + (p_2 * (real_field) / (-real_field))") 24 | amplitude2 = ne.evaluate("real_field**2 + imag_field**2") 25 | amplitude = ne.evaluate("sqrt(amplitude2)") 26 | return theta, amplitude 27 | 28 | 29 | def source(xy, t=0, position=(0, 0), k=1, amplitude=1, ang_freq=1, phase=(0, 0)): 30 | r2 = ne.evaluate("sum((xy-position)**2, axis=2)") 31 | r = ne.evaluate("sqrt(r2)") 32 | real_phase = phase[0] 33 | imag_phase = phase[1] 34 | reals = ne.evaluate("amplitude * cos(k * r - ang_freq * t + real_phase)") 35 | complexes = ne.evaluate("amplitude * sin(k * r - ang_freq * t + imag_phase)") 36 | 37 | return reals, complexes 38 | 39 | 40 | sources = [{'position': (0, -10)}, {'position': (0, 10)}] 41 | spos, fields = field(sources, t=0) 42 | print('superposition:') 43 | print(spos) 44 | print('fields:') 45 | print(fields) 46 | phase_field, _ = polar_field(spos) 47 | print(phase_field) -------------------------------------------------------------------------------- /hallucinator/transform.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | 4 | IDENTITY3 = np.array([[1, 0, 0], 5 | [0, 1, 0], 6 | [0, 0, 1]]) 7 | 8 | IDENTITY4 = np.array([[1, 0, 0, 0], 9 | [0, 1, 0, 0], 10 | [0, 0, 1, 0], 11 | [0, 0, 0, 1]]) 12 | 13 | '''transformation matrices''' 14 | 15 | ORTHO_PROJECT = np.array([[1, 0, 0, 0], 16 | [0, 1, 0, 0], 17 | [0, 0, 0, 0], 18 | [0, 0, 0, 1]]) 19 | 20 | 21 | def weak_project(z_factor=0.02): 22 | return np.array([[1, 0, 0, 0], 23 | [0, 1, 0, 0], 24 | [0, 0, 1, 0], 25 | [0, 0, z_factor, 0]]) 26 | 27 | 28 | # TODO mirror arbitrary axis 29 | 30 | def translate(tx=0, ty=0): 31 | return np.array([[1, 0, tx], 32 | [0, 1, ty], 33 | [0, 0, 1]]) 34 | 35 | 36 | def rotate(theta): 37 | return np.array([[math.cos(theta), -math.sin(theta), 0], 38 | [math.sin(theta), math.cos(theta), 0], 39 | [0, 0, 1]]) 40 | 41 | 42 | def scale(sx=1, sy=1): 43 | return np.array([[sx, 0, 0], 44 | [0, sy, 0], 45 | [0, 0, 1]]) 46 | 47 | 48 | def shear(sx=0, sy=0): 49 | return np.array([[1, sx, 0], 50 | [sy, 1, 0], 51 | [0, 0, 1]]) 52 | 53 | 54 | # TODO make general 55 | def mirror(axis='x'): 56 | if axis == 'x': 57 | return np.array([[-1, 0, 0], 58 | [0, 1, 0], 59 | [0, 0, 1]]) 60 | elif axis == 'y': 61 | return np.array([[1, 0, 0], 62 | [0, -1, 0], 63 | [0, 0, 1]]) 64 | else: 65 | print('mirror: invalid axis') 66 | 67 | 68 | '''2d chained transforms''' 69 | 70 | 71 | def shear_about(sx=0, sy=0, p=(0, 0)): 72 | return np.matmul(np.matmul(translate(p[0], p[1]), shear(sx, sy)), translate(-p[0], -p[1])) 73 | 74 | 75 | def mirror_about(axis='x', offset=0): 76 | if axis == 'x': 77 | return np.matmul(np.matmul(translate(tx=offset), mirror(axis)), translate(tx=-offset)) 78 | 79 | 80 | def rotate_about(theta, p=(0, 0)): 81 | return np.matmul(np.matmul(translate(p[0], p[1]), rotate(theta)), translate(-p[0], -p[1])) 82 | 83 | 84 | def scale_about(sx, sy, p=(0, 0)): 85 | return np.matmul(np.matmul(translate(p[0], p[1]), scale(sx, sy)), translate(-p[0], -p[1])) 86 | 87 | 88 | '''3d transforms''' 89 | 90 | 91 | # TODO mirroring 92 | 93 | 94 | def rotate_3(theta, axis=(1, 0, 0)): 95 | l, m, n = axis 96 | u = (1 - math.cos(theta)) 97 | cos = math.cos(theta) 98 | 99 | sin = math.sin(theta) 100 | return np.array([[l * l * u + cos, 101 | m * l * u - n * sin, 102 | n * l * u + m * sin, 103 | 0], 104 | [l * m * u + n * sin, 105 | m * m * u + cos, 106 | n * m * u - l * sin, 107 | 0], 108 | [l * n * u - m * sin, 109 | m * n * u + l * sin, 110 | n * n * u + cos, 111 | 0], 112 | [0, 113 | 0, 114 | 0, 115 | 1]]) 116 | 117 | 118 | def translate_3(t): 119 | return np.array([[1, 0, 0, t[0]], 120 | [0, 1, 0, t[1]], 121 | [0, 0, 1, t[2]], 122 | [0, 0, 0, 1]]) 123 | 124 | 125 | def scale_3(sx, sy, sz): 126 | return np.array([[sx, 0, 0, 0], 127 | [0, sy, 0, 0], 128 | [0, 0, sz, 0], 129 | [0, 0, 0, 1]]) 130 | 131 | 132 | def shear_3(xy=0, xz=0, yx=0, yz=0, zx=0, zy=0): 133 | return np.array([[1, xy, xz, 0], 134 | [yx, 1, yz, 0], 135 | [zx, zy, 1, 0], 136 | [0, 0, 0, 1]]) 137 | 138 | 139 | '''3d chained transformations''' 140 | 141 | 142 | def rotate_about_3(theta, axis=(1, 0, 0), p=(0, 0, 0)): 143 | if p == (0, 0, 0): 144 | return rotate_3(theta, axis) 145 | else: 146 | return np.matmul(np.matmul(translate_3(p), 147 | rotate_3(theta, axis)), 148 | translate_3(p)) 149 | 150 | 151 | def scale_about_3(sx, sy, sz, p=(0, 0, 0)): 152 | if p == (0, 0, 0): 153 | return scale_3(sx, sy, sz) 154 | else: 155 | return np.matmul(np.matmul(translate_3(p), 156 | scale_3(sx, sy, sz)), 157 | translate_3((-p[0], -p[1], -p[2]))) 158 | 159 | 160 | def shear_about_3(xy=0, xz=0, yx=0, yz=0, zx=0, zy=0, p=(0, 0, 0)): 161 | if p == (0, 0, 0): 162 | return shear_3(xy, xz, yx, yz, zx, zy) 163 | else: 164 | return np.matmul(np.matmul(translate_3(p), 165 | shear_3(xy, xz, yx, yz, zx, zy)), 166 | translate_3((-p[0], -p[1], -p[2]))) 167 | -------------------------------------------------------------------------------- /hallucinator/treasury.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import numexpr as ne 4 | from typing import TypedDict, NamedTuple, Tuple 5 | 6 | 7 | def wave(p, r, amp, f, phase): 8 | z = ne.evaluate("amp * sin(r * f + phase)") 9 | #z = z.reshape((z.shape[0] * z.shape[1])) 10 | #p = p.reshape((p.shape[0] * p.shape[1], 2)).transpose() 11 | return np.array([p[:, :, 0], p[:, :, 1], z]) 12 | 13 | 14 | def gen_spiral(coil_density: float = 1, 15 | radius: float = 1): 16 | 17 | def spiral(p): 18 | r = radius 19 | c = coil_density 20 | tau = 2 * math.pi 21 | x = ne.evaluate("cos(p * tau) * r") 22 | y = ne.evaluate("p / c") 23 | z = ne.evaluate("sin(p * tau) * r") 24 | return np.array([x, y, z]) 25 | 26 | return spiral 27 | 28 | 29 | def gen_plane_wave(amplitude: float = 1, 30 | frequency: float = 1, 31 | direction: Tuple[float, float] = (0, 1), 32 | phase: float = 0): 33 | 34 | def plane_wave(p): 35 | amp = amplitude 36 | f = frequency 37 | d = direction 38 | ph = phase 39 | r = ne.evaluate("sum(p * direction, axis=2)") 40 | return wave(p, r, amp, f, phase) 41 | 42 | return plane_wave 43 | 44 | 45 | def gen_ripple(amplitude: float = 1, 46 | frequency: float = 1, 47 | phase: float = 0): 48 | 49 | def ripple(p): 50 | amp = amplitude 51 | f = frequency 52 | ph = phase 53 | x2y2 = ne.evaluate("sum(p**2, axis=2)") 54 | r = ne.evaluate("sqrt(x2y2)") 55 | return wave(p, r, amp, f, phase) 56 | 57 | return ripple 58 | -------------------------------------------------------------------------------- /hallucinator/wave.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import hallucinator as hl 3 | import math 4 | import operator 5 | 6 | 7 | def propagating_disturbance(f, v): 8 | return lambda p, t: f(p - v * t) 9 | 10 | 11 | # TODO 2d propagate in both directions 12 | # TODO generalized disturbance propagation in n dimensions 13 | 14 | 15 | def propagating_disturbance_2d(f, v, fade_factor=1): 16 | return lambda a, b, t: fade_factor ** np.linalg.norm((a, b)) \ 17 | * f(np.linalg.norm((a, b)) - v * t) 18 | 19 | 20 | def plane_wave_t(f, v, direction=(0, 1), phase=0): 21 | return lambda a, b, t: f(np.dot((a, b), direction) - v * t + phase) 22 | 23 | 24 | def damped_harmonic(amplitude, frequency, damping_coeff): 25 | return lambda u: 0 if u > 0 else hl.sin_wave(amplitude=amplitude, frequency=frequency)(u) \ 26 | * math.exp(u * damping_coeff) 27 | 28 | 29 | '''def harmonic(amplitude, wavelength, frequency): 30 | k = 2 * math.pi / wavelength 31 | v = frequency * wavelength 32 | return lambda p, t: (p, amplitude * math.sin(k * (p - v * t)))''' 33 | 34 | 35 | def sin_wave(amplitude, frequency, phase=0): 36 | return lambda u: amplitude * math.sin(u * frequency + phase) 37 | 38 | 39 | def plane_wave(amplitude, frequency, direction=(0, 1), phase=0): 40 | return lambda a, b: (a, b, amplitude * math.sin(np.dot((a, b), direction) * frequency + phase)) 41 | 42 | 43 | '''def wave_2(f, v, source=(0, 0), falloff=0, starttime='eternal', defaultval=0): 44 | func = lambda a, b, t: (a, b, f(math.sqrt((source[0] - a) ** 2 + (source[1] - b) ** 2) - v * t) 45 | * np.e ** (-falloff * math.sqrt((source[0] - a) ** 2 + (source[1] - b) ** 2))) 46 | if starttime == 'eternal': 47 | return func 48 | else: 49 | return lambda a, b, t: (a, b, defaultval) if t < starttime else func(a, b, (t - starttime)) 50 | 51 | 52 | def superposition(f1, f2): 53 | return lambda x, y, t: tuple(map(operator.add, f1(x, y, t), f2(x, y, t)))''' 54 | 55 | -------------------------------------------------------------------------------- /profile/scenes.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | import hallucinator as hl 4 | 5 | 6 | def surface(amplitude: float = 1, frequency: float = 1, 7 | direction: Tuple[float, float] = (0, 1), phase: float = 0, 8 | rotate_x: float = 0, 9 | location: Tuple[int, int, int] = (0, 0, 20), 10 | camera_pos: Tuple[int, int, int] = (0, 0, 0)): 11 | resolution = 200 12 | x_range = (-5, 5) 13 | y_range = (-5, 5) 14 | projection_type = 'weak' 15 | render_density = 10 16 | surface_func = hl.plane_wave(amplitude, frequency, direction=direction, phase=phase) 17 | surface_obj = hl.ParaObject3(surface_func, 18 | region_type='2d', 19 | region_params={'a_range': (-3, 3), 20 | 'b_range': (-3, 3), 21 | 'a_length': 'auto', 22 | 'b_length': 'auto'}, 23 | species='surface').rotate(theta=rotate_x, axis=(1, 0, 0)).translate(location) 24 | scene = hl.MonochromeScene() 25 | 26 | scene.add_object(surface_obj, name='surface') 27 | return hl.camscene(scene, camera_pos, 28 | render_density=render_density, 29 | projection_type=projection_type, 30 | styles='line', 31 | x_range=x_range, 32 | y_range=y_range, 33 | resolution=resolution,) 34 | 35 | 36 | 37 | for i in range(100): 38 | surface() 39 | -------------------------------------------------------------------------------- /readme/birth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/birth.png -------------------------------------------------------------------------------- /readme/born.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/born.png -------------------------------------------------------------------------------- /readme/hungry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/hungry.png -------------------------------------------------------------------------------- /readme/pairproduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pairproduction.png -------------------------------------------------------------------------------- /readme/pattern1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pattern1.png -------------------------------------------------------------------------------- /readme/pattern2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pattern2.png -------------------------------------------------------------------------------- /readme/pattern3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pattern3.png -------------------------------------------------------------------------------- /readme/pattern4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pattern4.png -------------------------------------------------------------------------------- /readme/pinch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pinch.png -------------------------------------------------------------------------------- /readme/pinch_rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pinch_rainbow.png -------------------------------------------------------------------------------- /readme/pinch_tornado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/pinch_tornado.png -------------------------------------------------------------------------------- /readme/soup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/soup.png -------------------------------------------------------------------------------- /readme/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/ui.png -------------------------------------------------------------------------------- /readme/waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/waves.png -------------------------------------------------------------------------------- /readme/waves2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/waves2.png -------------------------------------------------------------------------------- /readme/yingyang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/readme/yingyang.png -------------------------------------------------------------------------------- /test/2d_test.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | 3 | scene = hl.MonochromeScene() 4 | scene.add_object(hl.rectangle(h=30, w=40), "rect") 5 | 6 | rect_arr = scene.render_scene(x_range=(-50, 50), y_range=(-50, 50), densities=1, projection_type=hl.Projections.ORTHO) 7 | hl.render_from_array(rect_arr) -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/__init__.py -------------------------------------------------------------------------------- /test/clothoid.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | 3 | clothoid = hl.plot_clothoid(padding=5, plot_range=(0, 400), resolution=6) 4 | hl.render_from_array(clothoid) 5 | -------------------------------------------------------------------------------- /test/deprecated/2d_wave_gradient.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | 5 | f = lambda y: 3 / (2 * y ** 2 + 1) 6 | wf = hallucinator.wave_2(f, v=2, source=(1, 1)) 7 | print('wf(0, 0, 0): ', wf(0, 0, 0)) 8 | print('wf(0, 0, 1): ', wf(0, 0, 1)) 9 | print('wf(0, 1, 1): ', wf(0, 1, 1)) 10 | print('wf(1, 0, 1): ', wf(1, 0, 1)) 11 | 12 | frame1 = hallucinator.gradient_frame(f=wf, 13 | t=0, 14 | white_ref=4, 15 | black_ref=0, 16 | x_range=(-5, 5), 17 | y_range=(-5, 5), 18 | resolution=20) 19 | frame2 = hallucinator.gradient_frame(f=wf, 20 | t=1, 21 | white_ref=4, 22 | black_ref=0, 23 | x_range=(-5, 5), 24 | y_range=(-5, 5), 25 | resolution=20) 26 | 27 | hallucinator.render_from_array(frame1) 28 | hallucinator.render_from_array(frame2) 29 | 30 | hallucinator.wave_2_gradient_video(wf, 31 | t_range=(0, 5), 32 | x_range=(-5, 5), 33 | y_range=(-5, 5), 34 | resolution=5, 35 | white_ref=4, 36 | black_ref=0, 37 | fps=4, 38 | filename='propw2d') 39 | -------------------------------------------------------------------------------- /test/deprecated/3d_camera_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | import random 8 | 9 | scene = hl.MonochromeScene() 10 | 11 | yz = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 12 | v1=(0, 1, 0), 13 | v2=(0, 0, 1)), 14 | a_range=(-5, 5), 15 | b_range=(-5, 5)) 16 | 17 | xy = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 18 | v1=(0, 1, 0), 19 | v2=(1, 0, 0)), 20 | a_range=(-5, 5), 21 | b_range=(-5, 5)) 22 | 23 | xz = hl.surface(surface_func=hl.plane(p0=(0, 0, 0), 24 | v1=(0, 0, 1), 25 | v2=(1, 0, 0)), 26 | a_range=(-5, 5), 27 | b_range=(-5, 5)) 28 | 29 | scene.add_object(xy, name="xy") 30 | scene.add_object(yz, name="yz") 31 | scene.add_object(xz, name="xz") 32 | 33 | x_transl = 0 34 | y_transl = 0 35 | z_transl = 15 36 | 37 | camera_pos = hl.IDENTITY4 38 | 39 | 40 | def rotate(theta, axis): 41 | return np.matmul(camera_pos, hl.rotate_3(theta, axis)) 42 | 43 | 44 | translate = np.matmul(camera_pos, hl.translate_3(x_transl, y_transl, z_transl)) 45 | 46 | '''scene.render_scene(x_range=(-25, 25), 47 | y_range=(-25, 25), 48 | camera_position=camera_pos, 49 | resolution=50, 50 | projection_type="weak", 51 | style='line', 52 | region_params={'a_spacing': 0.5, 53 | 'b_spacing': 0.5}, 54 | foreground=hl.WHITE, 55 | background=hl.BLACK, 56 | display=True)''' 57 | 58 | rps = 1 / 3 59 | period = 1/rps 60 | video_length = 3 * period / 4 61 | framerate = 8 62 | 63 | hl.video2(frame_function=lambda t: scene.render_scene(params={'basin': {'t': t}}, 64 | x_range=(-25, 25), 65 | y_range=(-25, 25), 66 | camera_position=np.matmul(np.matmul(camera_pos, 67 | rotate(theta=t * 2 * math.pi * rps, 68 | axis=(0, 1, 0) if t < period / 4 69 | else ((0, 0, 1) if t < period / 2 70 | else (1, 0, 0)))), 71 | translate), 72 | resolution=50, 73 | projection_type="weak", 74 | style='line', 75 | region_params={'a_spacing': 1, 76 | 'b_spacing': 1}, 77 | foreground=hl.WHITE, 78 | background=hl.BLACK, 79 | display=False), 80 | filename='camera_rotate_test_fast', 81 | frame_arguments=np.linspace(0, video_length, math.ceil(video_length*framerate)), 82 | fps=framerate, 83 | parallel=True) 84 | -------------------------------------------------------------------------------- /test/deprecated/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/deprecated/__init__.py -------------------------------------------------------------------------------- /test/deprecated/arrows.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | scene = hl.MonochromeScene() 8 | 9 | scene.add_object(hl.arrow(p0=(0, 0), direction=(1, 0), length=1, head_length=0.3), name="arrow1") 10 | scene.add_object(hl.arrow(p0=(1, 1), direction=(0, 1), length=1, head_length=0.3), name="arrow2") 11 | scene.add_object(hl.arrow(p0=(-3, -3), direction=(1, 5), length=3, head_length=0.3), name="arrow3") 12 | 13 | 14 | scene.render_scene(x_range=(-10, 10), 15 | y_range=(-10, 10), 16 | resolution=50, 17 | density=20, 18 | display=True) 19 | -------------------------------------------------------------------------------- /test/deprecated/camera_path.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/deprecated/camera_path.py -------------------------------------------------------------------------------- /test/deprecated/camera_position.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import numpy as np 6 | import math 7 | 8 | scene = hl.MonochromeScene() 9 | 10 | scene.add_object(hl.box(10, 10, 10), name='box') 11 | scene.add_object(hl.box(10, 10, 10, p0=(0, 20, 20)), name='box2') 12 | scene.add_object(hl.box(10, 10, 10, p0=(20, 0, 20)), name='box3') 13 | scene.add_object(hl.box(10, 10, 10, p0=(0, 0, 40)), name='box4') 14 | 15 | 16 | #scene.render_scene(x_range=(-40, 80), y_range=(-40, 80), display=True, resolution=10) 17 | 18 | scene = scene.transform(hl.translate_3(-20, -20, 50)) 19 | 20 | scene.render_scene(x_range=(-40, 40), y_range=(-40, 40), projection_type='weak', display=True, resolution=15) 21 | 22 | camera_position = hl.IDENTITY4 23 | 24 | scene.render_scene(x_range=(-40, 40), y_range=(-40, 40), 25 | camera_position=camera_position, 26 | projection_type='weak', 27 | display=True, 28 | resolution=15) 29 | 30 | camera_position = np.matmul(hl.translate_3(20, 20, 20), camera_position) 31 | 32 | scene.render_scene(x_range=(-40, 40), y_range=(-40, 40), 33 | camera_position=camera_position, 34 | projection_type='weak', 35 | display=True, 36 | resolution=15) 37 | 38 | camera_position = np.matmul(hl.rotate_about_3(theta=-math.pi / 8, axis=(0, 1, 0), p=(20, 20, 20)), camera_position) 39 | 40 | 41 | scene.render_scene(x_range=(-40, 40), y_range=(-40, 40), 42 | camera_position=camera_position, 43 | projection_type='weak', 44 | display=True, 45 | resolution=15) 46 | 47 | camera_position = np.matmul(hl.rotate_about_3(theta=math.pi / 8, axis=(1, 0, 0), p=(20, 20, 20)), camera_position) 48 | 49 | scene.render_scene(x_range=(-40, 40), y_range=(-40, 40), 50 | camera_position=camera_position, 51 | projection_type='weak', 52 | display=True, 53 | resolution=15) 54 | -------------------------------------------------------------------------------- /test/deprecated/colors.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | 6 | hallucinator.save_img(hallucinator.canvas(1000, 1000, color=(0, 0, 0)), filename='black') 7 | '''hallucinator.save_img(hallucinator.canvas(1000, 1000, color=(0, 0, 254)), filename='almost_blue') 8 | hallucinator.save_img(hallucinator.canvas(1000, 1000, color=(1, 0, 255)), filename='blue_with_red') 9 | hallucinator.save_img(hallucinator.canvas(1000, 1000, color=(10, 0, 255)), filename='blue_with_more_red')''' 10 | 11 | -------------------------------------------------------------------------------- /test/deprecated/conditional_region.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | 6 | topdown_gradient = lambda x, y: (x, y, -y) 7 | 8 | conditions = (lambda x, y: y < x, 9 | lambda x, y: y > x ** 2 - 4) 10 | 11 | gradient_along_path = hallucinator.conditional_region(f=topdown_gradient, 12 | conditions=conditions, 13 | x_range=(-5, 5), 14 | y_range=(-5, 5), 15 | density=10) 16 | 17 | gradient_image = hallucinator.set_to_gradient(points=gradient_along_path, 18 | x_range=(-5, 5), 19 | y_range=(-5, 5), 20 | black_ref=-5, 21 | white_ref=5, 22 | resolution=20, 23 | default=hallucinator.BLACK) 24 | 25 | # hallucinator.render_from_array(gradient_image) 26 | hallucinator.save_img(gradient_image, 'conditional') 27 | -------------------------------------------------------------------------------- /test/deprecated/de_field.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | 8 | def y_slope(x, y): 9 | #fun = -x / y 10 | #fun = 1 - 1/(x + y) 11 | #fun = math.cos(x) - y 12 | #fun = x**2 + y**2 13 | fun = ((1 - x**2) * y - x) / y 14 | #fun = (-x + math.sqrt(x**2 + 4 * y)) / 2 15 | return x, y, (1, fun) 16 | 17 | 18 | def at(params): 19 | return y_slope(**params) 20 | 21 | 22 | points = hl.surface_region(at, params={}, a_range=(-2, 2), b_range=(-2, 2), density=10, a_name='x', b_name='y') 23 | 24 | scene = hl.slope_field(points, arrow_length=0.1, arrow_head_length=0.02) 25 | 26 | scene.render_scene(x_range=(-3, 3), y_range=(-3, 3), resolution=300, density=150, background=hl.BLUE, 27 | display=True, save=True, filename='de_field') 28 | -------------------------------------------------------------------------------- /test/deprecated/disturbance_3d.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | import math 6 | 7 | 8 | def at_t(t, scene, backdrop): 9 | return scene.render_scene(params={'wave': {'t': t}}, 10 | x_range=(-20, 20), 11 | y_range=(-20, 20), 12 | resolution=20, 13 | density=10, 14 | foreground=hallucinator.WHITE, 15 | background=hallucinator.RED, 16 | display=False, 17 | backdrop=backdrop) 18 | 19 | 20 | scene = hallucinator.MonochromeScene() 21 | 22 | spiral = lambda p: (math.cos(p * 2 * math.pi) * 10, p * 10, math.sin(p * 2 * math.pi) * 10) 23 | 24 | disturbance = lambda u: 3 / (2 * u ** 2 + 1) 25 | #disturbance = lambda u: math.sin(u*50) 26 | 27 | backdrop = hallucinator.MonochromeScene() 28 | 29 | backdrop.add_object(hallucinator.axes(x_range=(-20, 20), 30 | y_range=(-20, 20)), "axes") 31 | 32 | backdrop_arr = backdrop.render_scene(x_range=(-20, 20), 33 | y_range=(-20, 20), 34 | resolution=20, 35 | density=5, 36 | foreground=hallucinator.WHITE, 37 | background=hallucinator.RED) 38 | 39 | scene.add_object(hallucinator.disturbance_on_path_3(disturbance=disturbance, 40 | v=2, 41 | init_pos=-2, 42 | polarization=(0, 1, 0), 43 | path=spiral, 44 | p_range=(-2, 2), 45 | path_length=4 * 2 * 5 * math.pi).translate(tz=50).project("weak", z_factor=0.03), 46 | name="wave") 47 | 48 | hallucinator._deprecated_video(frame_func=lambda t: at_t(t, scene, backdrop_arr), 49 | filename='3d_smooth_disturbance_test', 50 | t_range=(0, 5), 51 | FPS=20) 52 | -------------------------------------------------------------------------------- /test/deprecated/disturbance_on_path.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | 8 | def at_t(t, scene, backdrop): 9 | return scene.render_scene(params={'wave': {'t': t}}, 10 | x_range=(-10, 10), 11 | y_range=(-10, 10), 12 | resolution=40, 13 | density=20, 14 | foreground=hl.WHITE, 15 | background=hl.BLACK, 16 | display=False, 17 | backdrop=backdrop) 18 | 19 | 20 | scene = hl.MonochromeScene() 21 | 22 | path = hl.line_parametric(p0=(0, 0), dx=1, dy=0.5) 23 | f = lambda u: 3 / (2 * u ** 2 + 1) 24 | disturbance = hl.propagating_disturbance(f, v=2) 25 | 26 | backdrop = hl.MonochromeScene() 27 | 28 | backdrop.add_object(hl.axes(x_range=(-10, 10), 29 | y_range=(-10, 10)), "axes") 30 | 31 | backdrop_arr = backdrop.render_scene(x_range=(-10, 10), 32 | y_range=(-10, 10), 33 | resolution=40, 34 | density=10, 35 | foreground=hl.WHITE, 36 | background=hl.BLACK) 37 | 38 | # TODO fix this 39 | scene.add_object(hl.disturbance_on_path(disturbance=disturbance, 40 | polarization=(-0.5, 1), 41 | init_pos=-10, 42 | path=path, 43 | p_range=(-10, 10), 44 | path_length=math.sqrt(10 ** 2 + 5 ** 2)), 45 | name="wave") 46 | 47 | hl._deprecated_video(frame_func=lambda t: at_t(t, scene, backdrop_arr), 48 | filename='2d_disturbance_test', 49 | t_range=(0, 10), 50 | FPS=20) 51 | -------------------------------------------------------------------------------- /test/deprecated/experiment.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | frequency = 75 9 | amplitude = 1 10 | 11 | f = lambda u: amplitude * math.sin(frequency * u / (2 * np.pi)) 12 | 13 | center1 = (0, 0.3) 14 | center2 = (0, -0.3) 15 | 16 | source1 = hl.wave_2(f=f, v=1, source=center1, falloff=0) 17 | source2 = hl.wave_2(f=f, v=1, source=center2, falloff=0) 18 | 19 | superposition = hl.superposition(source1, source2) 20 | 21 | scene = hl.GrayscaleScene() 22 | 23 | wave = hl.ParaObject2(f=superposition, 24 | region_type="surface", 25 | region_params={"a_range": (-10, 10), 26 | "b_range": (-10, 10), 27 | "a_name": 'x', 28 | "b_name": 'y'}, 29 | species='wave_superposition') 30 | 31 | scene.add_object(wave.rotate(theta=math.pi / 2, p=(0, 0)), name='wave') 32 | 33 | hl._deprecated_video(frame_func=lambda t: scene.render_scene(params={'wave': {'t': t}}, 34 | x_range=(-12, 12), 35 | y_range=(-12, 12), 36 | resolution=20, 37 | density=5, 38 | white_ref=2.0, 39 | black_ref=-2.0, 40 | display=False, 41 | default=hl.GRAY), 42 | filename='two_slit2', 43 | t_range=(1, 5), 44 | FPS=10) 45 | -------------------------------------------------------------------------------- /test/deprecated/gradient.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | import numpy as np 5 | import math 6 | 7 | f = lambda x, y: math.sin(500 * math.sqrt(x**2 + y**2) / (2 * np.pi)) 8 | 9 | arr = hallucinator.phasegrid(f, (-5, 5), (-5, 5), resolution=100) 10 | 11 | grad = hallucinator.arr_to_gradient(arr, black_ref=-2.0, white_ref=2.0) 12 | 13 | hallucinator.render_from_array(grad) 14 | -------------------------------------------------------------------------------- /test/deprecated/harmonic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | 5 | f = lambda y: 3 / (2 * y ** 2 + 1) 6 | wf = hallucinator.harmonic(amplitude=1, wavelength=3, frequency=5) 7 | 8 | hallucinator.plot_profile(f=wf, 9 | t=0, 10 | x_range=(-5, 5), 11 | y_range=(-5, 5), 12 | background=hallucinator.BLUE, 13 | density=100) 14 | 15 | 16 | '''hallucinator.wave_video(f=wf, 17 | t_range=(-4, 4), 18 | x_range=(-5, 5), 19 | y_range=(-5, 5), 20 | background=hallucinator.RED, 21 | FPS=15, 22 | density=100, 23 | filename='harmonic')''' 24 | -------------------------------------------------------------------------------- /test/deprecated/lattice_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | import math 6 | import numpy as np 7 | 8 | 9 | def rotating_box(t, background): 10 | speed = 0.2 11 | frame = hallucinator.MonochromeScene() 12 | vector = (math.sin(t), math.cos(t), math.sin(t)) 13 | mag = math.sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2) 14 | normal_vec = (vector[0] / mag, vector[1] / mag, vector[2] / mag) 15 | cross = hallucinator.cross(width=10, depth=5, span=20, l_height=40, u_height=20, origin=(-5, -2.5, -30)) 16 | frame.add_object(cross.rotate(theta=math.cos(t) * speed * math.pi * t, axis=normal_vec).project(method='ortho'), 17 | name="lattice") 18 | 19 | return frame.render_scene(x_range=(-40, 40), 20 | y_range=(-40, 40), 21 | resolution=5, 22 | density=3, 23 | foreground=hallucinator.WHITE, 24 | background=hallucinator.RED, 25 | backdrop=background, 26 | display=False) 27 | 28 | 29 | canvas = hallucinator.MonochromeScene() 30 | 31 | background = canvas.render_scene(x_range=(-40, 40), 32 | y_range=(-40, 40), 33 | resolution=5, 34 | density=1, 35 | foreground=hallucinator.WHITE, 36 | background=hallucinator.RED, 37 | display=False) 38 | 39 | hallucinator._deprecated_video(frame_func=lambda t: rotating_box(t, background), 40 | filename='latticetest', 41 | t_range=(0, 20), 42 | FPS=20) 43 | -------------------------------------------------------------------------------- /test/deprecated/more_ripples.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | import operator 5 | 6 | 7 | f = lambda y: 3 / (2 * y ** 2 + 1) 8 | source1 = hallucinator.wave_2(f, v=3, source=(2.5, 0), falloff=0.5, starttime=1) 9 | source2 = hallucinator.wave_2(f, v=3, source=(2, 0), falloff=0.5, starttime=1.5) 10 | source3 = hallucinator.wave_2(f, v=3, source=(1, 0), falloff=0.5, starttime=2) 11 | source4 = hallucinator.wave_2(f, v=3, source=(0, 0), falloff=0.5, starttime=2.5) 12 | source5 = hallucinator.wave_2(f, v=3, source=(-1, 0), falloff=0.5, starttime=3) 13 | source6 = hallucinator.wave_2(f, v=3, source=(-1.5, 0), falloff=0.5, starttime=3.5) 14 | source7 = hallucinator.wave_2(f, v=3, source=(-1.5, 0), falloff=0.5, starttime=4) 15 | source8 = hallucinator.wave_2(f, v=3, source=(-1.5, 0), falloff=0.5, starttime=4.5) 16 | source9 = hallucinator.wave_2(f, v=3, source=(-1.5, 0), falloff=0.5, starttime=5) 17 | 18 | superposition = lambda x, y, t: tuple(map(operator.add, source1(x, y, t), source2(x, y, t))) 19 | superposition2 = lambda x, y, t: tuple(map(operator.add, superposition(x, y, t), source3(x, y, t))) 20 | superposition3 = lambda x, y, t: tuple(map(operator.add, superposition2(x, y, t), source4(x, y, t))) 21 | superposition4 = lambda x, y, t: tuple(map(operator.add, superposition3(x, y, t), source5(x, y, t))) 22 | superposition5 = lambda x, y, t: tuple(map(operator.add, superposition4(x, y, t), source6(x, y, t))) 23 | superposition6 = lambda x, y, t: tuple(map(operator.add, superposition5(x, y, t), source7(x, y, t))) 24 | superposition7 = lambda x, y, t: tuple(map(operator.add, superposition6(x, y, t), source8(x, y, t))) 25 | superposition8 = lambda x, y, t: tuple(map(operator.add, superposition7(x, y, t), source9(x, y, t))) 26 | 27 | hallucinator._deprecated_video(frame_func=lambda t: hallucinator.gradient_frame(f=superposition8, 28 | p={'t': t}, 29 | x_range=(-5, 5), 30 | y_range=(-5, 5), 31 | resolution=10, 32 | white_ref=10.0, 33 | black_ref=0), 34 | filename='doesthisstillwork2', 35 | t_range=(0, 5.5), 36 | FPS=10) 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/deprecated/new_optics_test.py: -------------------------------------------------------------------------------- 1 | # use object module in 2d 2 | import sys 3 | 4 | sys.path.append('../hallucinator') 5 | import hallucinator as hl 6 | import math 7 | import random 8 | 9 | surface = lambda a, b: (a, b) 10 | source1 = hl.PointSource(source=(-0.001, 0)) 11 | source2 = hl.PointSource(source=(0.001, 0)) 12 | source3 = hl.PointSource(source=(0, 0)) 13 | source4 = hl.PointSource(source=(0, -100)) 14 | 15 | num_sources = 2 16 | slit_width = 0.01 17 | interval = slit_width / num_sources 18 | line_of_sources = [] 19 | for i in range(num_sources): 20 | #phase = random.uniform(0, 2*math.pi) 21 | phase = 0 22 | line_of_sources.append(hl.PointSource(source=(-slit_width/2 + i * 2 * interval, 0), phase_offset=phase)) 23 | 24 | 25 | planewave1 = hl.PlaneWave(source=(0, 0), direction=(0, 1)) 26 | planewave2 = hl.PlaneWave(source=(0, 0), direction=(1, 0)) 27 | 28 | '''print(source1.path_length(location=(1, 1))) 29 | print(planewave1.path_length(location=(1, 0))) 30 | print(planewave1.path_length(location=(0, 1))) 31 | print(planewave2.path_length(location=(1, 0))) 32 | print(planewave2.path_length(location=(0, 1))) 33 | print(source1.phase_at(location=(0, 0))) 34 | print(source1.phase_at(location=(1, 0))) 35 | print(source1.phase_at(location=(0.000000045, 0))) 36 | print(source1.detect(location=(0, 0))) 37 | print(source1.detect(location=(1, 0))) 38 | print(hl.cart_to_polar(hl.field_at(sources=[source1], location=(0, 0)))) 39 | print(hl.cart_to_polar(hl.field_at(sources=[source1], location=(1, 0)))) 40 | print(hl.cart_to_polar(hl.field_at(sources=[source2], location=(1, 0)))) 41 | print(hl.intensity_at(sources=[source1], location=(1, 0))) 42 | print(hl.intensity_at(sources=[source2], location=(1, 0))) 43 | print(hl.intensity_at(sources=[source1, source2], location=(1, 0))) 44 | print(hl.intensity_at(sources=[source1, source2], location=(1.01, 0)))''' 45 | 46 | a_range = (-1, 1) 47 | b_range = (-1, 1) 48 | 49 | intensities = hl.eval_surface_intensity(sources=[*line_of_sources], 50 | surface=surface, 51 | a_range=a_range, 52 | b_range=b_range, 53 | a_density=2000, 54 | b_density=2000, 55 | # density=100 56 | ) 57 | canv = hl.set_to_gradient(intensities, 58 | x_range=a_range, 59 | y_range=b_range, 60 | black_ref=0, 61 | white_ref=4.0, 62 | default=hl.GRAY, 63 | resolution=2000, 64 | ) 65 | 66 | hl.render_from_array(canv) 67 | hl.save_img(canv, 'width{0}_{1}-points_random'.format(slit_width, num_sources)) 68 | #hl.save_img(canv, 'two_point_stacked') 69 | 70 | -------------------------------------------------------------------------------- /test/deprecated/obj_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | import math 5 | 6 | scene = hallucinator.MonochromeScene() 7 | 8 | scene.add_object(hallucinator.vector(p1=(0, 0), p2=(20, 20)), name='vector') 9 | 10 | scene.render_scene(x_range=(-40, 40), 11 | y_range=(-40, 40), 12 | resolution=20, 13 | density=2, 14 | foreground=hallucinator.WHITE, 15 | background=hallucinator.BLACK, 16 | display=True) 17 | -------------------------------------------------------------------------------- /test/deprecated/optics_phase_test.py: -------------------------------------------------------------------------------- 1 | # use object module in 2d 2 | import sys 3 | 4 | sys.path.append('../hallucinator') 5 | import hallucinator as hl 6 | import math 7 | import random 8 | 9 | surface = lambda a, b: (a, b) 10 | source1 = hl.PointSource(source=(-0, 0)) 11 | planewave1 = hl.PlaneWave(source=(0, 0), direction=(0, 1)) 12 | 13 | a_range = (-5, 5) 14 | b_range = (-5, 5) 15 | 16 | intensities = hl.eval_surface(sources=[source1, planewave1], 17 | surface=surface, 18 | a_range=a_range, 19 | b_range=b_range, 20 | a_density=100, 21 | b_density=100) 22 | 23 | canv = hl.set_to_gradient(intensities, 24 | x_range=a_range, 25 | y_range=b_range, 26 | black_ref=0, 27 | white_ref=4.0, 28 | default=hl.GRAY, 29 | resolution=100) 30 | 31 | hl.render_from_array(canv) 32 | # hl.save_img(canv, 'two_point_stacked') 33 | -------------------------------------------------------------------------------- /test/deprecated/optics_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | surface = lambda a, b: (a, b, 10) 9 | source = (0, 0, 0) 10 | direction = (0, 0, 1) 11 | 12 | planewave = hl.PlaneWave(source=source, wavelength=0.05, amplitude=1, init_phase=0, direction=direction) 13 | 14 | spherewave = hl.PointSource(source=source, wavelength=0.05, amplitude=1, init_phase=math.pi/2) 15 | 16 | points = hl.eval_surface_intensity(sources=(planewave, spherewave), 17 | surface=surface, 18 | a_range=(-15, 15), 19 | b_range=(-15, 15), 20 | density=15) 21 | 22 | canv = hl.set_to_gradient(points, 23 | x_range=(-15, 15), 24 | y_range=(-15, 15), 25 | black_ref=0, 26 | white_ref=4.0, 27 | default=hl.GRAY, 28 | resolution=50) 29 | 30 | hl.render_from_array(canv) 31 | hl.save_img(canv, 'w0.05-d10-15-15-p0.5pi') -------------------------------------------------------------------------------- /test/deprecated/optics_test_3d.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/deprecated/optics_test_3d.py -------------------------------------------------------------------------------- /test/deprecated/parameter_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | 6 | 7 | def at_t(t, scene): 8 | return scene.render_scene(params={'wave': {'t': t}}, 9 | x_range=(-10, 10), 10 | y_range=(-10, 10), 11 | resolution=40, 12 | density=5, 13 | foreground=hallucinator.WHITE, 14 | background=hallucinator.BLACK, 15 | display=False) 16 | 17 | 18 | scene = hallucinator.MonochromeScene() 19 | 20 | scene.add_object(hallucinator.axes(x_range=(-10, 10), 21 | y_range=(-10, 10)), "axes") 22 | f = lambda u: 3 / (2 * u ** 2 + 1) 23 | #TODO this is broken by update on disturbance_on_path 24 | scene.add_object(hallucinator.disturbance_on_path(f=f, v=2, x_range=(-10, 10)), "wave") 25 | 26 | hallucinator._deprecated_video(frame_func=lambda t: at_t(t, scene), 27 | filename='t_param_test', 28 | t_range=(0, 10), 29 | FPS=20) 30 | -------------------------------------------------------------------------------- /test/deprecated/path_grad_vid.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | import math 5 | import numpy as np 6 | 7 | frequency = 100 8 | amplitude = 1 9 | 10 | f = lambda u: amplitude * math.sin(frequency * u / (2 * np.pi)) 11 | 12 | source1 = hallucinator.wave_2(f=f, v=1, source=(0, 0), falloff=0.5) 13 | 14 | rect_region = lambda fu: hallucinator.surface_region(f=fu, 15 | x_range=(-1, 1), 16 | y_range=(-2, 2), 17 | density=10) 18 | 19 | 20 | 21 | hallucinator._deprecated_video(frame_func=lambda t: hallucinator.regional_gradient_frame(f=source1, 22 | t=t, 23 | region=rect_region, 24 | x_range=(-5, 5), 25 | y_range=(-5, 5), 26 | resolution=5, 27 | white_ref=1.0, 28 | black_ref=-1.0, 29 | default=hallucinator.RED), 30 | filename='grad_path_vid_test', 31 | t_range=(1, 10), 32 | FPS=10) 33 | -------------------------------------------------------------------------------- /test/deprecated/path_gradient.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | 5 | topdown_discrete = lambda x, y: (x, y, -1 if y < 0 else 1) 6 | topdown_gradient = lambda x, y: (x, y, -y) 7 | 8 | path = lambda p: (p, p) 9 | p_range = [-3, 3] 10 | gradient_along_path = hallucinator.path_region(f=topdown_gradient, 11 | path=path, 12 | p_range=p_range, 13 | density=5) 14 | 15 | gradient_image = hallucinator.set_to_gradient(points=gradient_along_path, 16 | x_range=(-5, 5), 17 | y_range=(-5, 5), 18 | black_ref=-3, 19 | white_ref=3, 20 | default=hallucinator.BLUE) 21 | 22 | # hallucinator.render_from_array(gradient_image) 23 | hallucinator.save_img(gradient_image, 'path_grad_test0') 24 | -------------------------------------------------------------------------------- /test/deprecated/path_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | import math 6 | 7 | scene = hallucinator.MonochromeScene() 8 | 9 | spiral = lambda p: (math.cos(p * 2 * math.pi) * math.e ** (-p), math.sin(p * 2 * math.pi) * math.e ** (-p)) 10 | 11 | scene.add_object(hallucinator.path(path_func=spiral, p_range=(0, 5), path_length=10 * math.pi), name="spiral") 12 | 13 | scene.render_scene(x_range=(-2, 2), 14 | y_range=(-2, 2), 15 | resolution=200, 16 | density=10, 17 | foreground=hallucinator.WHITE, 18 | background=hallucinator.BLACK, 19 | display=True) 20 | 21 | -------------------------------------------------------------------------------- /test/deprecated/plane.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | 6 | 7 | def scene_at_t(t): 8 | frame = hallucinator.MonochromeScene() 9 | frame.add_object(hallucinator.plane_section(p0=(t * 20 - 50, 0, 0), 10 | v1=(0, 1, 0), 11 | v2=(1, 0, 1), 12 | a_range=(-100, 100), 13 | b_range=(0, 200)).project(method='weak'), "plane") 14 | 15 | return frame.render_scene(x_range=(-30, 30), 16 | y_range=(-30, 30), 17 | resolution=20, 18 | density=50, 19 | foreground=hallucinator.WHITE, 20 | background=hallucinator.BLACK, 21 | display=False) 22 | 23 | 24 | hallucinator._deprecated_video(frame_func=lambda t: scene_at_t(t), 25 | filename='plane_test_3', 26 | t_range=(0, 10), 27 | FPS=20) 28 | -------------------------------------------------------------------------------- /test/deprecated/polygon_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | 6 | canvas = hallucinator.MonochromeScene() 7 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(-70, -10)), "axes1") 8 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(-70, 40)), "axes2") 9 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(-70, -60)), "axes3") 10 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(-20, -10)), "axes4") 11 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(-20, 40)), "axes5") 12 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(-20, -60)), "axes6") 13 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(30, -10)), "axes7") 14 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(30, 40)), "axes8") 15 | canvas.add_object(hallucinator.axes((0, 40), (0, 40), origin=(30, -60)), "axes9") 16 | 17 | background = canvas.render_scene(x_range=(-80, 80), 18 | y_range=(-80, 80), 19 | resolution=10, 20 | density=1, 21 | foreground=hallucinator.RED, 22 | background=hallucinator.BLACK, 23 | display=False) 24 | 25 | frame = hallucinator.MonochromeScene() 26 | # hallucinator.render_from_array(background) 27 | frame.add_object(hallucinator.polygon(30, 3).translate(-60, 40), "triangle") 28 | frame.add_object(hallucinator.polygon(25, 4).translate(-60, -10), "square") 29 | frame.add_object(hallucinator.polygon(20, 5).translate(-60, -60), "pentagon") 30 | frame.add_object(hallucinator.polygon(15, 6).translate(-10, 40), "hexagon") 31 | frame.add_object(hallucinator.polygon(13, 7).translate(-10, -10), "heptagon") 32 | frame.add_object(hallucinator.polygon(11, 8).translate(-10, -60), "octagon") 33 | 34 | frame.add_object(hallucinator.polygon(10, 9).translate(45, 40), "enneagon") 35 | frame.add_object(hallucinator.polygon(9, 10).translate(45, -10), "decagon") 36 | frame.add_object(hallucinator.polygon(8, 11).translate(45, -60), "undecagon") 37 | 38 | frame.render_scene(x_range=(-80, 80), 39 | y_range=(-80, 80), 40 | resolution=10, 41 | density=2, 42 | foreground=hallucinator.WHITE, 43 | background=hallucinator.BLACK, 44 | backdrop=background, 45 | display=True) 46 | -------------------------------------------------------------------------------- /test/deprecated/ripples.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | import math 5 | import operator 6 | import numpy as np 7 | 8 | f = lambda u: math.sin(100 * u / (2*np.pi)) / u 9 | source1 = hallucinator.wave_2(f, v=2, source=(3, 0), falloff=0.5) 10 | source2 = hallucinator.wave_2(f, v=2, source=(-3, 0), falloff=0.5) 11 | '''source3 = hallucinator.wave_2(f, v=50, source=(0, 0)) 12 | source4 = hallucinator.wave_2(f, v=50, source=(1, 0)) 13 | source5 = hallucinator.wave_2(f, v=50, source=(2, 0))''' 14 | #source6 = hallucinator.wave_2(f, v=50, source=(1, 0)) 15 | #source7 = hallucinator.wave_2(f, v=50, source=(2, 0)) 16 | #source8 = hallucinator.wave_2(f, v=50, source=(3, 0)) 17 | #source9 = hallucinator.wave_2(f, v=50, source=(9, 0)) 18 | 19 | superposition = lambda x, y, t: tuple(map(operator.add, source1(x, y, t), source2(x, y, t))) 20 | '''superposition2 = lambda x, y, t: tuple(map(operator.add, superposition(x, y, t), source3(x, y, t))) 21 | superposition3 = lambda x, y, t: tuple(map(operator.add, superposition2(x, y, t), source4(x, y, t))) 22 | superposition4 = lambda x, y, t: tuple(map(operator.add, superposition3(x, y, t), source5(x, y, t)))''' 23 | #superposition5 = lambda x, y, t: tuple(map(operator.add, superposition4(x, y, t), source6(x, y, t))) 24 | #superposition6 = lambda x, y, t: tuple(map(operator.add, superposition5(x, y, t), source7(x, y, t))) 25 | #superposition7 = lambda x, y, t: tuple(map(operator.add, superposition6(x, y, t), source8(x, y, t))) 26 | #superposition8 = lambda x, y, t: tuple(map(operator.add, superposition7(x, y, t), source9(x, y, t))) 27 | 28 | 29 | hallucinator.wave_2_gradient_video(superposition, 30 | t_range=(0, 10), 31 | x_range=(-10, 10), 32 | y_range=(-10, 10), 33 | resolution=10, 34 | white_ref=2.0, 35 | black_ref=-2.0, 36 | fps=10, 37 | filename='ripples_falloff_8') 38 | -------------------------------------------------------------------------------- /test/deprecated/shear_3d.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | import numpy as np 7 | 8 | 9 | def rotating_box(t, background): 10 | frame = hl.MonochromeScene() 11 | 12 | for i in range(3): 13 | frame.add_object(hl.box(30, 30, 30, (-50 + 50 * i, 25, 30)), "box{0}".format(i)) 14 | 15 | for i in range(3): 16 | frame.add_object(hl.box(30, 30, 30, (-50 + 50 * i, -40, 30)), "box{0}".format(i+3)) 17 | 18 | frame.objects["box0"] = frame.objects["box0"].shear(xy=math.sin(t), p=(-50, 25, 30)) 19 | frame.objects["box1"] = frame.objects["box1"].shear(xz=math.sin(t), p=(0, 25, 30)) 20 | frame.objects["box2"] = frame.objects["box2"].shear(yx=math.sin(t), p=(50, 25, 30)) 21 | frame.objects["box3"] = frame.objects["box3"].shear(yz=math.sin(t), p=(-50, -40, 30)) 22 | frame.objects["box4"] = frame.objects["box4"].shear(zx=math.sin(t), p=(0, -40, 30)) 23 | frame.objects["box5"] = frame.objects["box5"].shear(zy=math.sin(t), p=(50, -40, 30)) 24 | 25 | for i in range(3): 26 | frame.objects["box{0}".format(i)] = frame.objects["box{0}".format(i)].rotate(math.pi / 8, 27 | (1, 0, 0), 28 | (-50 + -50 * i, 25, 30)) 29 | frame.objects["box{0}".format(i)] = frame.objects["box{0}".format(i)].project("ortho") 30 | 31 | for i in range(3, 6): 32 | frame.objects["box{0}".format(i)] = frame.objects["box{0}".format(i)].rotate(math.pi / 8, 33 | (1, 0, 0), 34 | (-50 + -50 * i, -40, 30)) 35 | frame.objects["box{0}".format(i)] = frame.objects["box{0}".format(i)].project("ortho") 36 | 37 | return frame.render_scene(x_range=(-100, 100), 38 | y_range=(-100, 100), 39 | resolution=5, 40 | density=3, 41 | foreground=hl.WHITE, 42 | background=hl.RED, 43 | backdrop=background, 44 | display=False) 45 | 46 | 47 | canvas = hl.MonochromeScene() 48 | 49 | # canvas.add_object(hallucinator.axes((-50, 50), (-50, 50), origin=(0, 0)), "axes1") 50 | 51 | background = canvas.render_scene(x_range=(-100, 100), 52 | y_range=(-100, 100), 53 | resolution=5, 54 | density=1, 55 | foreground=hl.WHITE, 56 | background=hl.RED, 57 | display=False) 58 | 59 | hl._deprecated_video(frame_func=lambda t: rotating_box(t, background), 60 | filename='cube_shear_test', 61 | t_range=(0, 5), 62 | FPS=20) 63 | 64 | -------------------------------------------------------------------------------- /test/deprecated/sphere.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | 8 | def rotating_sphere(t): 9 | speed = 0.7 10 | frame = hl.MonochromeScene() 11 | vector = hl.np.array([1, 0, 0]) 12 | frame.add_object( 13 | hl.sphere(center=(10, 0, 30), radius=10).rotate(theta=math.pi / 2, 14 | axis=(0, 1, 0), p=(10, 0, 30)).rotate(theta=t * speed, 15 | axis=vector, 16 | p=(10, 0, 30)).project( 17 | method='weak'), 18 | name="sphere") 19 | 20 | frame.add_object( 21 | hl.sphere(center=(10, 0, 30), radius=7).rotate(theta=math.pi / 2, 22 | axis=(0, 1, 0), p=(10, 0, 30)).rotate(theta=-t * speed, 23 | axis=vector, 24 | p=(10, 0, 30)).project( 25 | method='weak'), 26 | name="sphere2") 27 | 28 | return frame.render_scene(x_range=(-10, 50), 29 | y_range=(-30, 30), 30 | resolution=20, 31 | density=5, 32 | style='wireframe', 33 | region_params={'a_spacing': 2, 34 | 'b_spacing': 2}, 35 | foreground=(51, 255, 255), 36 | background=(112, 50, 50), 37 | display=False) 38 | 39 | 40 | hl._deprecated_video(frame_func=lambda t: rotating_sphere(t), 41 | filename='sphere_in_sphere', 42 | t_range=(0, 10), 43 | FPS=20) 44 | -------------------------------------------------------------------------------- /test/deprecated/superzp.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import math 3 | 4 | zp = hl.fourier_zp(10) 5 | zp_pi = hl.fourier_zp(10, phase=math.pi) 6 | zp1 = hl.fourier_zp(15, (-0.5, 0)) 7 | zp1_pi = hl.fourier_zp(15, (-0.5, 0), math.pi) 8 | zp2 = hl.fourier_zp(15, (0.5, 0)) 9 | zp2_pi = hl.fourier_zp(15, (0.5, 0), math.pi) 10 | 11 | plane = hl.sampling_image(lambda p: abs(zp1(p) + zp2(p)), value_range=(-10, 10), image_size=(1000, 1000), binary=False) 12 | hl.render_from_array(plane) 13 | 14 | '''plane = hl.sampling_image(zp, value_range=(-10, 10), image_size=(800, 800), binary=False) 15 | hl.render_from_array(plane) 16 | 17 | plane = hl.sampling_image(zp_pi, value_range=(-10, 10), image_size=(800, 800), binary=False) 18 | hl.render_from_array(plane)''' -------------------------------------------------------------------------------- /test/deprecated/surface.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | scene = hl.MonochromeScene() 8 | 9 | f = lambda a, b: (a, 0.2 * math.sin(a * math.pi) ** 2 + 0.2 * math.sin(b * math.pi) + 0.5 / (abs(a) + abs(b)), b) 10 | 11 | scene.add_object(hl.surface(surface_func=f, 12 | a_range=(-5, 5), 13 | b_range=(-5, 5)).rotate(theta=-math.pi / 8, 14 | axis=(1, 0, 0)).rotate(math.pi/8, (0, 1, 0)).translate(tz=25).project("weak"), 15 | name="cursedlasagna") 16 | 17 | scene.add_object(hl.axes_3((-6, 6), (-6, 6), (-6, 6)).rotate(theta=-math.pi / 8, 18 | axis=(1, 0, 0)).rotate(math.pi/8, (0, 1, 0)).translate(tz=25).project("weak"), 19 | name='axes') 20 | 21 | scene.render_scene(x_range=(-6, 6), 22 | y_range=(-6, 6), 23 | resolution=150, 24 | density=50, 25 | display=True, 26 | background=hl.BLUE) 27 | -------------------------------------------------------------------------------- /test/deprecated/textured_path.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | scene = hl.MonochromeScene() 8 | 9 | spiral = lambda p: (math.cos(p * 2 * math.pi) * 10, p * 10, math.sin(p * 2 * math.pi) * 10) 10 | 11 | texture = lambda u: math.sin(u * 50) 12 | 13 | scene.add_object(hl.textured_path(texture=texture, 14 | pos=-2, 15 | polarization=(0, 1, 0), 16 | path=spiral, 17 | p_range=(-2, 2), 18 | path_length=4 * 2 * 5 * math.pi).translate(tz=60).project("weak", z_factor=0.03), 19 | name="wavy_spiral") 20 | 21 | scene.render_scene(x_range=(-10, 10), 22 | y_range=(-10, 10), 23 | resolution=40, 24 | density=20, 25 | foreground=hl.WHITE, 26 | background=hl.BLACK, 27 | display=True) 28 | -------------------------------------------------------------------------------- /test/deprecated/textured_surface.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | scene = hl.MonochromeScene() 8 | texture = lambda u: 2 * -math.sin(u * math.pi) / u 9 | 10 | scene.add_object(hl.textured_surface(texture=texture, 11 | pos=(0, 0), 12 | polarization=(-1, 0, 0), 13 | surface=hl.plane(p0=(0, 0, 0), 14 | v1=(0, 1, 0), 15 | v2=(0, 0, 1)), 16 | a_range=(-15, 15), 17 | b_range=(-15, 15)).rotate(theta=math.pi / 8, 18 | axis=(0, 1, 0)).translate( 19 | tz=30).project("weak"), 20 | name='basin') 21 | 22 | scene.render_scene(x_range=(-20, 20), 23 | y_range=(-20, 20), 24 | resolution=50, 25 | style='line', 26 | region_params={'a_spacing': 0.5, 27 | 'b_spacing': 0.5}, 28 | foreground=hl.BLUE, 29 | background=hl.BLACK, 30 | display=True) 31 | -------------------------------------------------------------------------------- /test/deprecated/transform_3_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | import math 6 | import numpy as np 7 | 8 | 9 | def rotating_box(t, background): 10 | speed = 0.5 11 | frame = hallucinator.MonochromeScene() 12 | vector = (math.sin(t), math.cos(t), math.sin(t)) 13 | mag = math.sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2) 14 | normal_vec = (vector[0] / mag, vector[1] / mag, vector[2] / mag) 15 | frame.add_object(hallucinator.box(20, 20, 20, p0=(-10, -10, -10)).rotate(theta=math.cos(t) * speed * math.pi * t, 16 | axis=normal_vec).project(method='ortho'), 17 | name="box") 18 | 19 | return frame.render_scene(x_range=(-30, 30), 20 | y_range=(-30, 30), 21 | resolution=5, 22 | density=3, 23 | foreground=hallucinator.WHITE, 24 | background=hallucinator.RED, 25 | backdrop=background, 26 | display=False) 27 | 28 | 29 | canvas = hallucinator.MonochromeScene() 30 | 31 | background = canvas.render_scene(x_range=(-30, 30), 32 | y_range=(-30, 30), 33 | resolution=5, 34 | density=1, 35 | foreground=hallucinator.WHITE, 36 | background=hallucinator.RED, 37 | display=False) 38 | 39 | hallucinator._deprecated_video(frame_func=lambda t: rotating_box(t, background), 40 | filename='cubetest', 41 | t_range=(0, 20), 42 | FPS=20) 43 | -------------------------------------------------------------------------------- /test/deprecated/transform_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator as hl 5 | import math 6 | 7 | 8 | def scene_at_t(t, background): 9 | frame = hl.MonochromeScene() 10 | # hl.render_from_array(background) 11 | frame.add_object(hl.square(20, p0=(-60, -60)), "nothing") 12 | 13 | frame.add_object(hl.square(20, p0=(-60, -10)).rotate(theta=math.pi * t, 14 | p=(-50, 0)), 15 | "rotate clockwise") 16 | 17 | frame.add_object(hl.square(20, p0=(-60, 40)).rotate(theta=math.pi * -t, 18 | p=(-50, 50)), 19 | "rotate counterclockwise") 20 | 21 | frame.add_object(hl.square(20, p0=(-10, -60)).translate(tx=math.sin(t * math.pi) * 10), 22 | "x translate") 23 | 24 | frame.add_object(hl.square(20, p0=(-10, -10)).translate(ty=math.cos(t * math.pi) * 10), 25 | "y translate") 26 | frame.add_object(hl.square(20, p0=(-10, 40)).translate(tx=math.sin(t * math.pi) * 10, 27 | ty=math.cos(t * math.pi) * 10), 28 | "x and y translate") 29 | 30 | '''frame.add_object(hl.square(20, p0=(40, -60)).scale(sx=math.sin(t * math.pi) * 2, 31 | p=(50, -50)), 32 | "x scale") 33 | 34 | frame.add_object(hl.square(20, p0=(40, -10)).scale(sy=math.cos(t * math.pi) * 2, 35 | p=(50, 0)), 36 | "y scale") 37 | frame.add_object(hl.square(20, p0=(40, 40)).scale(sx=math.sin(t * math.pi) * 2, 38 | sy=math.cos(t * math.pi) * 2, 39 | p=(50, 50)), 40 | "x and y scale")''' 41 | 42 | frame.add_object(hl.square(20, p0=(40, -60)).shear(sx=math.sin(t * math.pi), 43 | p=(50, -50)), 44 | "x shear") 45 | 46 | frame.add_object(hl.square(20, p0=(40, -10)).shear(sy=math.cos(t * math.pi), 47 | p=(50, 0)), 48 | "y shear") 49 | frame.add_object(hl.square(20, p0=(40, 40)).shear(sx=math.sin(t * math.pi), 50 | sy=math.cos(t * math.pi), 51 | p=(50, 50)), 52 | "x and y shear") 53 | 54 | return frame.render_scene( 55 | x_range=(-80, 80), 56 | y_range=(-80, 80), 57 | resolution=5, 58 | density=2, 59 | foreground=hl.WHITE, 60 | background=hl.BLACK, 61 | backdrop=background) 62 | 63 | 64 | canvas = hl.MonochromeScene() 65 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(-50, 0)), "axes1") 66 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(-50, 50)), "axes2") 67 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(-50, -50)), "axes3") 68 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(0, 0)), "axes4") 69 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(0, 50)), "axes5") 70 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(0, -50)), "axes6") 71 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(50, 0)), "axes7") 72 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(50, 50)), "axes8") 73 | canvas.add_object(hl.axes((-20, 20), (-20, 20), origin=(50, -50)), "axes9") 74 | 75 | background = canvas.render_scene(x_range=(-80, 80), 76 | y_range=(-80, 80), 77 | resolution=5, 78 | density=2, 79 | foreground=hl.WHITE, 80 | background=hl.RED) 81 | 82 | hl._deprecated_video(frame_func=lambda t: scene_at_t(t, background), 83 | filename='transform_test_6', 84 | t_range=(0, 10), 85 | FPS=20) 86 | -------------------------------------------------------------------------------- /test/deprecated/wave.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../hallucinator') 3 | import hallucinator 4 | 5 | f = lambda y: 3 / (2 * y ** 2 + 1) 6 | wf = hallucinator.wave(f, 2) 7 | print('wf(0, 0): ', wf(0, 0)) 8 | print('wf(0, 1): ', wf(0, 1)) 9 | print('wf(1, 0): ', wf(1, 0)) 10 | hallucinator.plot_profile(f=wf, 11 | t=0, 12 | x_range=(-5, 5), 13 | y_range=(-5, 5), 14 | density=50) 15 | 16 | 17 | hallucinator.wave_video(f=wf, 18 | t_range=(-4, 4), 19 | x_range=(-5, 5), 20 | y_range=(-2, 5), 21 | FPS=10, 22 | density=50) 23 | -------------------------------------------------------------------------------- /test/deprecated/weak_projection_test.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | sys.path.append('../hallucinator') 4 | import hallucinator 5 | import math 6 | 7 | 8 | def rotating_box(t, background): 9 | speed = 0.3 10 | frame = hallucinator.MonochromeScene() 11 | vector = (math.sin(t), math.cos(t), math.sin(t)) 12 | mag = math.sqrt(vector[0] ** 2 + vector[1] ** 2 + vector[2] ** 2) 13 | normal_vec = (vector[0] / mag, vector[1] / mag, vector[2] / mag) 14 | 15 | frame.add_object(hallucinator.box(20, 20, 20, 16 | p0=(10, 10, 25)).rotate(theta=math.cos(t) * speed * math.pi * t, 17 | axis=normal_vec, p=(20, 20, 35)).project( 18 | method='weak', 19 | z_factor=0.05), 20 | name="box") 21 | 22 | return frame.render_scene(x_range=(-30, 30), 23 | y_range=(-30, 30), 24 | resolution=5, 25 | density=3, 26 | foreground=hallucinator.WHITE, 27 | background=hallucinator.GREEN, 28 | backdrop=background, 29 | display=False) 30 | 31 | 32 | canvas = hallucinator.MonochromeScene() 33 | 34 | #TODO is origin working correctly? 35 | canvas.add_object(hallucinator.axes_3(x_range=(-500, 500), 36 | y_range=(-500, 500), 37 | z_range=(-50, 50), 38 | origin=(20, 20, 35)).project(method='weak'), name="axes") 39 | 40 | background = canvas.render_scene(x_range=(-30, 30), 41 | y_range=(-30, 30), 42 | resolution=5, 43 | density=1, 44 | foreground=hallucinator.WHITE, 45 | background=hallucinator.GREEN, 46 | display=False) 47 | 48 | hallucinator._deprecated_video(frame_func=lambda t: rotating_box(t, background), 49 | filename='weak_projection_test_green', 50 | t_range=(0, 20), 51 | FPS=20) 52 | -------------------------------------------------------------------------------- /test/grouptest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/grouptest.py -------------------------------------------------------------------------------- /test/hetero.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import math 3 | 4 | scene = hl.MonochromeScene() 5 | 6 | location = (5, -10, 40) 7 | location_2 = (0, 8, 60) 8 | spiral_location = (-7, -10, 10) 9 | rotate_x = math.pi / 4 10 | scene.add_object(hl.ParaObject3(hl.gen_ripple(amplitude=0.5, frequency=3, phase=0), 11 | region_type='2d', 12 | region_params={'surface_range': ((-10, 10), (-10, 10))}, 13 | species='surface').rotate(theta=rotate_x, axis=(1, 0, 0)).translate(location), 14 | "ripple") 15 | scene.add_object(hl.ParaObject3(hl.gen_ripple(amplitude=0.5, frequency=3, phase=0), 16 | region_type='2d', 17 | region_params={'surface_range': ((-10, 10), (-10, 10))}, 18 | species='surface').rotate(theta=-rotate_x, axis=(1, 1, 0)).translate(location_2), 19 | "ripple2") 20 | 21 | scene.add_object(hl.path_3(path_func=hl.gen_spiral(coil_density=1, radius=2), 22 | p_range=(0, 10), 23 | path_length=10 * math.pi).translate(spiral_location), 24 | "spiral") 25 | 26 | camscene = scene.render_scene(camera_position=(0, -3, -50), 27 | projection_type=hl.Projections.WEAK, 28 | styles={'ripple': hl.Styles.WIREFRAME, 29 | 'ripple2': hl.Styles.UNIFORM, 30 | 'spiral': hl.Styles.UNIFORM}, 31 | x_range=(-10, 10), 32 | y_range=(-10, 10), 33 | resolution=50, 34 | densities={'ripple': (3, 3), 35 | 'ripple2': (5, 5), 36 | 'spiral': 50}) 37 | 38 | hl.render_from_array(camscene) 39 | -------------------------------------------------------------------------------- /test/lattice.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/lattice.py -------------------------------------------------------------------------------- /test/mortaltest.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import math 3 | import numpy as np 4 | import numexpr as ne 5 | 6 | points = hl.path_points((0, 5), density=10) 7 | #print(points) 8 | 9 | a_axis, b_axis = hl.surface_points(((0, 5), (0, 5)), density=(1, 1)) 10 | print(a_axis) 11 | print(b_axis) 12 | 13 | 14 | spiral = lambda p: (math.cos(p * 2 * math.pi), 15 | p, 16 | math.sin(p * 2 * math.pi)) 17 | 18 | 19 | def batch_spiral(p): 20 | tau = 2 * math.pi 21 | x = ne.evaluate("cos(p * tau)") 22 | y = p 23 | z = ne.evaluate("sin(p * tau)") 24 | return np.array([x, y, z]) 25 | 26 | 27 | 28 | surface_func = hl.plane_wave(0, 1) 29 | 30 | spiral_sampled = hl.eval_path(spiral, points) 31 | print(spiral_sampled) 32 | spiral_sampled_2 = batch_spiral(points) 33 | print('batch:') 34 | print(spiral_sampled_2) 35 | 36 | # plane_sampled = hl.eval_surf(surface_func, a_axis, b_axis) 37 | # print(plane_sampled) 38 | # print(plane_sampled.shape) 39 | # 40 | # ones = np.ones(plane_sampled.shape[1]) 41 | # arr = np.vstack((plane_sampled, ones)) 42 | # print(arr) 43 | # rotated = np.matmul(hl.rotate_3(theta=math.pi/2), arr) 44 | # 45 | # print(rotated) -------------------------------------------------------------------------------- /test/ripple_surface.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | from typing import Tuple 3 | import math 4 | 5 | scene = hl.MonochromeScene() 6 | 7 | location = (0, 0, 20) 8 | rotate_x = math.pi / 4 9 | scene.add_object(hl.ParaObject3(hl.gen_ripple(amplitude=0.5, frequency=3, phase=0), 10 | region_type='2d', 11 | region_params={'surface_range': ((-10, 10), (-10, 10))}, 12 | species='surface').rotate(theta=rotate_x, axis=(1, 0, 0)).translate(location), "ripple") 13 | 14 | camscene = scene.render_scene(camera_position=(0, -3, -50), 15 | projection_type=hl.Projections.WEAK, 16 | styles=hl.Styles.UNIFORM, 17 | x_range=(-10, 10), 18 | y_range=(-10, 10), 19 | resolution=50, 20 | densities=(5, 20)) 21 | 22 | hl.render_from_array(camscene) 23 | -------------------------------------------------------------------------------- /test/sphere.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/test/sphere.py -------------------------------------------------------------------------------- /test/spiral.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import Tuple 3 | import numexpr as ne 4 | import numpy as np 5 | 6 | sys.path.append('../hallucinator') 7 | import hallucinator as hl 8 | import math 9 | 10 | 11 | def batch_spiral(p): 12 | tau = 2 * math.pi 13 | x = ne.evaluate("cos(p * tau)") 14 | y = p 15 | z = ne.evaluate("sin(p * tau)") 16 | return np.array([x, y, z]) 17 | 18 | 19 | def gen_spiral(location: Tuple[int, int, int] = (0, 0, 20), 20 | coil_density: float = 1, 21 | radius: float = 1, 22 | turns: float = 5, 23 | rotate_x: float = math.pi/4): 24 | 25 | spiral_obj = hl.path_3(path_func=hl.gen_spiral(coil_density=coil_density, radius=radius), 26 | p_range=(0, turns), 27 | path_length=10 * math.pi).translate(location).rotate(theta=rotate_x, axis=(1, 0, 0), p=location) 28 | return spiral_obj 29 | 30 | resolution = 200 31 | x_range = (-3, 3) 32 | y_range = (-3, 3) 33 | projection_type = 'weak' 34 | render_density = 100 35 | scene = hl.MonochromeScene() 36 | scene.add_object(gen_spiral(location=(0, 0, 20), 37 | coil_density=2, 38 | radius=2, 39 | turns=20, 40 | rotate_x=0), "spiral") 41 | 42 | camscene = scene.render_scene(camera_position=(0, 0, 0), 43 | projection_type=hl.Projections.WEAK, 44 | styles='path', 45 | x_range=(-10, 10), 46 | y_range=(-10, 10), 47 | resolution=50, 48 | densities=50) 49 | hl.render_from_array(camscene) -------------------------------------------------------------------------------- /test/wavy_surface.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | from typing import TypedDict, NamedTuple, Tuple 3 | import math 4 | 5 | 6 | def wavy_surface(amplitude: float = 1, 7 | frequency: float = 1, 8 | direction: float = 0, 9 | phase: float = 0, 10 | rotate_x: float = 0, 11 | rotate_y: float = 0, 12 | rotate_z: float = 0, 13 | location: Tuple[int, int, int] = (0, 0, 20)): 14 | 15 | surface_obj = hl.ParaObject3(hl.gen_plane_wave(amplitude, frequency, hl.unit_vector(direction), phase), 16 | region_type='2d', 17 | region_params={'surface_range': ((-5, 5), (-5, 5))}, 18 | species='surface') 19 | surface_obj = surface_obj.rotate(theta=rotate_x, axis=(1, 0, 0)) 20 | surface_obj = surface_obj.rotate(theta=rotate_y, axis=(0, 1, 0)) 21 | surface_obj = surface_obj.rotate(theta=rotate_z, axis=(0, 0, 1)) 22 | surface_obj = surface_obj.translate(location) 23 | return surface_obj 24 | 25 | 26 | def wavy_scene(t, **kwargs): 27 | scene = hl.MonochromeScene() 28 | scene.add_object(wavy_surface(amplitude=1, 29 | frequency=t, 30 | direction=0, 31 | phase=0, 32 | rotate_x=-1, 33 | rotate_y=4, 34 | rotate_z=1, 35 | location=(0, 0, 40)), 36 | "surface") 37 | 38 | camscene = scene.render_scene(camera_position=(0, 0, -15), 39 | projection_type=hl.Projections.WEAK, 40 | styles=hl.Styles.UNIFORM, 41 | x_range=(-7, 7), 42 | y_range=(-7, 7), 43 | resolution=75, 44 | densities=(6, 30)) 45 | 46 | return camscene 47 | 48 | 49 | hl.render_from_array(wavy_scene(t=0)) 50 | 51 | 52 | params = dict( 53 | frame_function=lambda d: wavy_scene(**d), 54 | frame_arguments=hl.unroll_dict(dict( 55 | t=hl.np.linspace(0, 37, num=1500), 56 | )), 57 | filename=f"../videos/lasagna3", 58 | fps=15, 59 | preview=True, 60 | parallel_frames=False, 61 | ) 62 | 63 | #hl.video(**params) 64 | -------------------------------------------------------------------------------- /test/wireframe.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | from typing import Tuple 3 | import math 4 | 5 | scene = hl.MonochromeScene() 6 | 7 | location = (0, 0, 20) 8 | rotate_x = math.pi / 4 9 | scene.add_object(hl.ParaObject3(hl.gen_ripple(amplitude=0.5, frequency=3, phase=0), 10 | region_type='2d', 11 | region_params={'surface_range': ((-10, 10), (-10, 10))}, 12 | species='surface').rotate(theta=rotate_x, axis=(1, 0, 0)).translate(location), "ripple") 13 | 14 | camscene = scene.render_scene(camera_position=(0, -3, -50), 15 | projection_type='weak', 16 | styles=hl.Styles.WIREFRAME, 17 | x_range=(-10, 10), 18 | y_range=(-10, 10), 19 | resolution=50, 20 | densities=(3, 3)) 21 | 22 | hl.render_from_array(camscene) 23 | -------------------------------------------------------------------------------- /test/wireframe_test.py: -------------------------------------------------------------------------------- 1 | import hallucinator as hl 2 | import math 3 | import numpy as np 4 | import numexpr as ne 5 | 6 | points = hl.surface_points(surface_range=((0, 4), (0, 4))) 7 | #print(points.shape) 8 | 9 | func = hl.gen_plane_wave() 10 | sampled = func(points) 11 | h1 = sampled[:, :, 1:] 12 | h2 = sampled[:, :, :-1] 13 | v1 = sampled[:, 1:, :] 14 | v2 = sampled[:, :-1, :] 15 | h1_reshaped = hl.reshape_array(h1) 16 | h2_reshaped = hl.reshape_array(h2) 17 | v1_reshaped = hl.reshape_array(v1) 18 | v2_reshaped = hl.reshape_array(v2) 19 | 20 | h1_transformed = np.matmul(hl.rotate_3(theta=math.pi/4, axis=(0, 0, 1)), h1_reshaped) 21 | h2_transformed = np.matmul(hl.rotate_3(theta=math.pi/4, axis=(0, 0, 1)), h2_reshaped) 22 | v1_transformed = np.matmul(hl.rotate_3(theta=math.pi/4, axis=(0, 0, 1)), v1_reshaped) 23 | v2_transformed = np.matmul(hl.rotate_3(theta=math.pi/4, axis=(0, 0, 1)), v2_reshaped) 24 | 25 | h = np.array((h1_transformed, h2_transformed)) 26 | h_transpose = h.transpose() 27 | h_swapped = np.swapaxes(h_transpose, 1, 2) 28 | v = np.array((v1_transformed, v2_transformed)) 29 | v_transpose = v.transpose() 30 | v_swapped = np.swapaxes(v_transpose, 1, 2) 31 | 32 | lines = np.concatenate((h_swapped, v_swapped), axis=0) 33 | 34 | im = hl.lines_to_bichrome(lines, (-10, 10), (-10, 10), resolution=50) 35 | hl.render_from_array(im) 36 | 37 | # print(sampled) 38 | # im = hl.points_to_bichrome(sampled.transpose(), (-10, 10), (-10, 10), resolution=50) 39 | # hl.render_from_array(im) -------------------------------------------------------------------------------- /test/zonepinch.py: -------------------------------------------------------------------------------- 1 | import math 2 | import sys 3 | import numpy as np 4 | sys.path.append('../hallucinator') 5 | 6 | import hallucinator as hl 7 | 8 | def birth(t, **kwargs): 9 | val_range = 200 10 | plane = np.zeros((val_range, val_range)) # hl.xy_plane(value_range=(0, val_range), resolution=val_range) 11 | size = 30 12 | # center = (val_range/2, val_range/2) 13 | split = val_range / 2 14 | init_offset = val_range / 4 15 | offset_scale = 100 16 | 17 | for x in range(val_range): 18 | for y in range(val_range): 19 | if x > split: 20 | if y > split: 21 | """ lower right """ 22 | plane[x][y] = math.sin(((x - split - offset_scale*math.cos(t)) ** 2 23 | + (y - split - offset_scale*math.sin(t)) ** 2 24 | + (offset_scale*math.cos(t))**2 + (offset_scale*math.sin(t))**2) 25 | / size + math.pi/2) 26 | else: 27 | """lower left""" 28 | plane[x][y] = 0 29 | plane[x][y] = math.sin(((x - split - offset_scale*math.cos(t)) ** 2 30 | - (y - split + offset_scale*math.sin(t)) ** 2 31 | - (offset_scale * math.cos(t)) ** 2 + (offset_scale*math.sin(t))**2) 32 | / size + math.pi/2) 33 | else: 34 | if y > split: 35 | """ upper right """ 36 | plane[x][y] = math.sin(((x - split + offset_scale*math.cos(t)) ** 2 37 | - (y - split - offset_scale*math.sin(t)) ** 2 38 | - (offset_scale * math.cos(t))**2 - (offset_scale*math.cos(t))**2) 39 | / size)# - math.pi / 2) 40 | else: 41 | """ upper left """ 42 | plane[x][y] = math.sin(((x - split + offset_scale*math.cos(t)) ** 2 43 | + (y - split + offset_scale*math.sin(t)) ** 2 44 | + (-offset_scale * math.cos(t))**2 + (offset_scale*math.sin(t))**2) 45 | / size) 46 | return hl.imagify(plane, hsv=False) 47 | 48 | 49 | #hl.render_from_array(hl.imagify(birth(1000))) 50 | '''filename = '../images/zonepinch{0}.png'.format(offset) 51 | hl.save_img(img, filename)''' 52 | 53 | params = dict( 54 | frame_function=lambda d: birth(**d), 55 | frame_arguments=hl.unroll_dict(dict( 56 | t=hl.np.linspace(0, math.pi, num=200), 57 | )), 58 | filename=f"../videos/birth", 59 | fps=10, 60 | preview=True, 61 | parallel_frames=False, 62 | ) 63 | 64 | hl.video(**params) 65 | 66 | -------------------------------------------------------------------------------- /ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/ui/__init__.py -------------------------------------------------------------------------------- /ui/_test.py: -------------------------------------------------------------------------------- 1 | import tkinter 2 | from enum import Enum 3 | from inspect import signature 4 | from typing import NamedTuple, Tuple, get_args 5 | 6 | import cv2 7 | import PIL.Image, PIL.ImageTk 8 | import numpy as np 9 | 10 | 11 | 12 | # TODO Add more 13 | class ColorStyle(Enum): 14 | GRAYSCALE = "grayscale" 15 | HSV = "hsv" 16 | ffsd = "sda" 17 | 18 | for color in ColorStyle: 19 | print(color) 20 | for color in ColorStyle.HSV.__class__: 21 | print(color) 22 | 23 | # TODO Add more 24 | class PlotStyle(Enum): 25 | CONTOUR = "contour" 26 | 27 | class ViewSettings(NamedTuple): 28 | style: ColorStyle = ColorStyle.HSV 29 | plot_type: PlotStyle = PlotStyle.CONTOUR 30 | value_range: Tuple[int, int] = (-1, 1) 31 | resolution: int = 500 32 | example_complex: complex = 1 33 | 34 | 35 | def get_param_info(func): 36 | return {name: param.default for name, param in signature(func).parameters.items()}, \ 37 | {name: param.annotation for name, param in signature(func).parameters.items()} 38 | 39 | 40 | print(get_param_info(ViewSettings)) 41 | print(type(signature(ViewSettings).parameters)) 42 | for name, param in signature(ViewSettings).parameters.items(): 43 | print(type(param), param) 44 | print(name, param.annotation, param.default) 45 | print(type(param.annotation)) 46 | try: 47 | print("type args", get_args(param.annotation)) 48 | except Exception as e: 49 | print(e) 50 | print("\n\n") 51 | 52 | print(PlotStyle(PlotStyle.CONTOUR)) 53 | 54 | generic_types = { 55 | int: (int, np.integer), 56 | float: (float, np.floating), 57 | complex: (complex, np.complexfloating), 58 | bool: (bool, np.bool), 59 | str: (str, np.str), 60 | tuple: (tuple, np.ndarray), 61 | Enum: (Enum,) 62 | } 63 | 64 | 65 | 66 | def convert_to_builtin(type_to_convert): 67 | generic_type = None 68 | for builtin_type, corresponding_types in generic_types.items(): 69 | if issubclass(type_to_convert, corresponding_types): 70 | generic_type = builtin_type 71 | return generic_type 72 | 73 | 74 | 75 | # 76 | # class App: 77 | # def __init__(self, window, window_title, image_path="imgs/close.gif"): 78 | # self.window = window 79 | # self.window.title(window_title) 80 | # 81 | # # Load an image using OpenCV 82 | # img = cv2.imread(image_path) 83 | # img = np.random.randint(0, 255, (1000, 1000), dtype=np.uint8) 84 | # print(img.shape, img) 85 | # self.cv_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 86 | # 87 | # # Get the image dimensions (OpenCV stores image data as NumPy ndarray) 88 | # self.height, self.width, no_channels = self.cv_img.shape 89 | # 90 | # # Create a canvas that can fit the above image 91 | # self.canvas = tkinter.Canvas(window, width =10, height=10)# self.width, height = self.height) 92 | # self.canvas.pack() 93 | # # Use PIL (Pillow) to convert the NumPy ndarray to a PhotoImage 94 | # self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(self.cv_img)) 95 | # 96 | # # Add a PhotoImage to the Canvas 97 | # self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW) 98 | # print(self.canvas.info()) 99 | # print(self.canvas.winfo_children()) 100 | # print(self.canvas) 101 | # # Button that lets the user blur the image 102 | # self.btn_blur=tkinter.Button(window, text="Blur", width=50, command=self.blur_image) 103 | # self.btn_blur.pack(anchor=tkinter.CENTER, expand=True) 104 | # 105 | # self.window.mainloop() 106 | # 107 | # # Callback for the "Blur" button 108 | # def blur_image(self): 109 | # self.cv_img = cv2.blur(self.cv_img, (3, 3)) 110 | # self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(self.cv_img)) 111 | # self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW) 112 | # 113 | # 114 | # # Create a window and pass it to the Application object 115 | # App(tkinter.Tk(), "Tkinter and OpenCV") -------------------------------------------------------------------------------- /ui/application.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import messagebox 3 | from tkinter import ttk 4 | 5 | from ui import controls 6 | from ui.display import DisplayTab 7 | 8 | 9 | # Needed for the tab bar style. It looks bad without it. 10 | def create_style(): 11 | close_image = tk.PhotoImage("closeImage", file="static/close.gif") 12 | style = ttk.Style() 13 | style.element_create("close", "image", "closeImage", border=0, sticky='') 14 | style.layout("TNotebook", [("TNotebook.client", {"sticky": "nswe"})]) 15 | style.layout("TNotebook.Tab", [("TNotebook.tab", 16 | {"sticky": "nswe", 17 | "children": [("TNotebook.label", {"side": "left"}), 18 | ("TNotebook.close", {"side": "left"})] 19 | } 20 | )]) 21 | return close_image 22 | 23 | 24 | class Application: 25 | 26 | # Create the application window 27 | def __init__(self, width, height): 28 | 29 | # Create the root 30 | self.root = tk.Tk() 31 | self.root.geometry("%dx%d+50+30" % (width, height)) 32 | self.root.title("Hallucinating") 33 | # self.root.maxsize(2000, 900) 34 | self.close_image = create_style() 35 | 36 | # Create the notebook and add a tab to it 37 | self.display_tabs = [] 38 | self.tab_count = 0 39 | self.notebook = ttk.Notebook(self.root) 40 | self.notebook.pack(fill=tk.BOTH, expand=1) 41 | s = ttk.Style() 42 | s.configure('TNotebook', tabposition='nw') 43 | self.create_tab() 44 | 45 | # Bind Button-1 to tab click so tabs can be closed 46 | self.notebook.bind('', self.tab_click) 47 | self.notebook.bind() 48 | 49 | # Build the menu bar 50 | self.build_menus() 51 | 52 | # Do final root prep 53 | self.root.update_idletasks() 54 | self.root.attributes('-topmost', True) 55 | self.root.update() 56 | self.root.attributes('-topmost', False) 57 | 58 | # Build the applications menubar 59 | def build_menus(self): 60 | menu_list = [("File", 61 | [('New Tab', 'Ctrl+N', '', self.create_tab), 62 | ('Quit', 'Ctrl+Q', '', self.quit_app)]), 63 | ] 64 | controls.create_menubar(self.root, menu_list) 65 | 66 | # Forward the given command to the current display tab 67 | def forward_command(self, command): 68 | if len(self.display_tabs) == 0: 69 | messagebox.showwarning("Error", "There is no data.") 70 | else: 71 | command(self.display_tabs[self.notebook.index("current")]) 72 | 73 | # Create a tab 74 | def create_tab(self, event=None): 75 | display = DisplayTab(self.notebook, self.root) 76 | display.frame.pack() 77 | self.tab_count += 1 78 | self.notebook.add(display.frame, text=f"Tab {self.tab_count}") 79 | self.display_tabs.append(display) 80 | 81 | # If the user clicks a close button, get the tab at that position and close it 82 | def tab_click(self, event): 83 | if "close" in event.widget.identify(event.x, event.y): 84 | index = self.notebook.index("@{},{}".format(event.x, event.y)) 85 | self.notebook.forget(index) 86 | self.display_tabs.pop(index) 87 | 88 | # Handle the user quitting the application 89 | def quit_app(self, event=None): 90 | self.root.destroy() 91 | 92 | # Let the application run 93 | def main(self): 94 | self.root.mainloop() 95 | 96 | 97 | # Create the display application and run it 98 | if __name__ == "__main__": 99 | app = Application(1200, 675) 100 | app.main() 101 | -------------------------------------------------------------------------------- /ui/deprecated/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/ui/deprecated/__init__.py -------------------------------------------------------------------------------- /ui/deprecated/application.py: -------------------------------------------------------------------------------- 1 | # import tkinter as tk 2 | # from tkinter import messagebox 3 | # from tkinter import ttk 4 | # 5 | # 6 | # # Create the close button style using the close image at imgs/close.png 7 | # from ui import controls 8 | # from ui.display import DisplayTab 9 | # 10 | # 11 | # def create_style(): 12 | # closeImage = tk.PhotoImage("closeImage", file="../imgs/close.gif") 13 | # style = ttk.Style() 14 | # style.element_create("close", "image", "closeImage", border=0, sticky='') 15 | # style.layout("TNotebook", [("TNotebook.client", {"sticky": "nswe"})]) 16 | # style.layout("TNotebook.Tab", [("TNotebook.tab", 17 | # {"sticky": "nswe", 18 | # "children": [("TNotebook.label", {"side": "left"}), 19 | # ("TNotebook.close", {"side": "left"})] 20 | # } 21 | # )]) 22 | # 23 | # return closeImage 24 | # 25 | # 26 | # class Application(object): 27 | # 28 | # # Create the application window 29 | # def __init__(self, width, height): 30 | # 31 | # # Create the root 32 | # self.root = tk.Tk() 33 | # self.root.geometry("%dx%d+50+30" % (width, height)) 34 | # self.root.title("ADAPT - A Data Analysis and Plotting Tool") 35 | # self.root.maxsize(2000, 900) 36 | # self.close_image = create_style() 37 | # 38 | # # Create the notebook and add a tab to it 39 | # self.displayTabs = [] 40 | # self.notebook = ttk.Notebook(self.root) 41 | # self.notebook.pack(fill=tk.BOTH, expand=1) 42 | # self.createTab() 43 | # 44 | # # Build the menu bar 45 | # self.buildMenus() 46 | # 47 | # # Bind Button-1 to tab click so tabs can be closed 48 | # self.notebook.bind('', self.tabClick) 49 | # self.notebook.bind() 50 | # 51 | # # Do final root prep 52 | # self.root.lift() 53 | # self.root.update_idletasks() 54 | # 55 | # # Build the applications menubar 56 | # def buildMenus(self): 57 | # menuList = [("File", 58 | # [('New Tab', 'Ctrl+N', '', self.createTab), 59 | # ('Quit', 'Ctrl+Q', '', self.quitApp)]), 60 | # ("Data", 61 | # [('Open Data Set', 'Ctrl+O', '', 62 | # lambda event=None: self.forwardCommand(DisplayTab.loadData)), 63 | # ('Save Data Set', 'Ctrl+S', '', 64 | # lambda event=None: self.forwardCommand(DisplayTab.saveData)), 65 | # ('Plotting Options', 'Ctrl+P', '', 66 | # lambda event=None: self.forwardCommand(DisplayTab. 67 | # plottingOptionsDialog)), 68 | # ('Clear Data', 'Ctrl+C', '', 69 | # lambda event=None: self.forwardCommand(DisplayTab.clearData)) 70 | # ]), 71 | # ("Analysis", 72 | # [('Principle Component Analysis', None, None, 73 | # lambda event=None: self.forwardCommand(DisplayTab.pcaDialog)), 74 | # ('Cluster Analysis', None, None, 75 | # lambda event=None: self.forwardCommand(DisplayTab.clusterDialog)), 76 | # ('Linear Regression', 'Ctrl+L', '', 77 | # lambda event=None: self.forwardCommand(DisplayTab.regressionDialog)), 78 | # ]), 79 | # ("View", 80 | # [('Reset view', 'Ctrl+T', '', 81 | # lambda event=None: self.forwardCommand(DisplayTab.resetView)), 82 | # ('Align to X-Y plane', 'Ctrl+1', '', 83 | # lambda event=None: self.forwardCommand(DisplayTab.resetView)), 84 | # ('Align to X-Z plane', 'Ctrl+2', '', 85 | # lambda event=None: self.forwardCommand(DisplayTab.gotoXZ)), 86 | # ('Align to Y-Z plane', 'Ctrl+3', '', 87 | # lambda event=None: self.forwardCommand(DisplayTab.gotoYZ)), 88 | # ('Change mouse sensitivity', 'Ctrl+S', '', 89 | # lambda event=None: self.forwardCommand(DisplayTab. 90 | # mouseSensitivityDialog)) 91 | # ]) 92 | # ] 93 | # controls.create_menubar(self.root, menuList) 94 | # 95 | # # Forward the given command to the current display tab 96 | # def forwardCommand(self, command): 97 | # if len(self.displayTabs) == 0: 98 | # messagebox.showwarning("Error", "There is no data.") 99 | # else: 100 | # command(self.displayTabs[self.notebook.index("current")]) 101 | # 102 | # # Create a tab 103 | # def createTab(self, event=None): 104 | # display = DisplayTab(self.notebook, self.root) 105 | # display.frame.pack() 106 | # self.notebook.add(display.frame, text='No Data') 107 | # self.displayTabs.append(display) 108 | # 109 | # # If the user clicks a close button, get the tab at that position and close it 110 | # def tabClick(self, event): 111 | # if "close" in event.widget.identify(event.x, event.y): 112 | # index = self.notebook.index("@{},{}".format(event.x, event.y)) 113 | # self.notebook.forget(index) 114 | # self.displayTabs.pop(index) 115 | # 116 | # 117 | # # Handle the user quitting the application 118 | # def quitApp(self, event=None): 119 | # self.root.destroy() 120 | # 121 | # # Let the application run 122 | # def main(self): 123 | # self.root.mainloop() 124 | # 125 | # 126 | # # Create the display application and run it 127 | # if __name__ == "__main__": 128 | # app = Application(1200, 675) 129 | # app.main() 130 | -------------------------------------------------------------------------------- /ui/deprecated/viz_3d.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | 3 | import numpy as np 4 | import math 5 | 6 | class View(object): 7 | 8 | # Initial viewing parameters 9 | def __init__(self): 10 | self.vrp = np.array([0.5, 0.5, 1], dtype=float) 11 | self.vpn = np.array([0, 0, -1], dtype=float) 12 | self.vup = np.array([0, 1, 0], dtype=float) 13 | self.u = np.array([-1, 0, 0], dtype=float) 14 | self.extent = np.array([1, 1, 1], dtype=float) 15 | self.screen = np.array([500, 500], dtype=float) 16 | self.offset = np.array([300, 100], dtype=float) 17 | 18 | # Resets the object to its default state 19 | def reset(self): 20 | self.__init__() 21 | 22 | # Creates a deep copy of the view object 23 | def clone(self): 24 | view = View() 25 | view.vrp = np.copy(self.vrp) 26 | view.vpn = np.copy(self.vpn) 27 | view.vup = np.copy(self.vup) 28 | view.u = np.copy(self.u) 29 | view.extent = np.copy(self.extent) 30 | view.screen = np.copy(self.screen) 31 | view.offset = np.copy(self.offset) 32 | return view 33 | 34 | # Build the np array representing the viewing transformation 35 | def build(self): 36 | # Build orthonormal basis 37 | tu = normalize(np.cross(self.vup, self.vpn)) 38 | tvup = normalize(np.cross(self.vpn, tu)) 39 | tvpn = normalize(np.copy(self.vpn)) 40 | self.u = tu 41 | self.vup = tvup 42 | self.vpn = tvpn 43 | 44 | # Translate the view matrix 45 | t1 = get_translation_matrix(-self.vrp[0], 46 | -self.vrp[1], 47 | -self.vrp[2]) 48 | 49 | # Rotate the view matrix 50 | r1 = get_rotation_matrix(tu, tvup, tvpn) 51 | 52 | # Translate the view matrix so the lower left corner 53 | # of the view space is at the origin 54 | t2 = get_translation_matrix(0.5 * self.extent[0], 55 | 0.5 * self.extent[1], 56 | 0) 57 | 58 | # Scale the view matrix to the screen 59 | s1 = get_scale_matrix(-self.screen[0] / self.extent[0], 60 | -self.screen[1] / self.extent[1], 61 | 1.0 / self.extent[2]) 62 | 63 | # Translate the view matrix so the lower left corner 64 | # is at the origin plus the view offset 65 | t3 = get_translation_matrix(self.screen[0] + self.offset[0], 66 | self.screen[1] + self.offset[1], 67 | 0) 68 | 69 | # Get the viewing pipeline matrix 70 | vtm = np.identity(4, float) 71 | vtm = reduce(np.dot, [t3, s1, t2, r1, t1, vtm]) 72 | return vtm 73 | 74 | 75 | # Rotate with respect to the origin 76 | def rotateOrigin(self, uAngle, vupAngle): 77 | # Axis align 78 | r1 = get_rotation_matrix(self.u, self.vup, self.vpn).T 79 | # Rotate U and VUP 80 | r2 = y_rot(vupAngle) 81 | r3 = x_rot(uAngle) 82 | # Get the rotation matrix and apply it to the axes 83 | rotate = reduce(np.dot, [r1.T, r3, r2, r1]) 84 | self.vrp = np.dot(rotate, np.append(self.vrp, [1]))[:3] 85 | self.u = normalize(np.dot(rotate, np.append(self.u, [0]))[:3]) 86 | self.vup = normalize(np.dot(rotate, np.append(self.vup, [0]))[:3]) 87 | self.vpn = normalize(np.dot(rotate, np.append(self.vpn, [0]))[:3]) 88 | 89 | # Rotate with respect to the VRC 90 | def rotateVRC(self, uAngle, vupAngle): 91 | # Translate to origin 92 | vrc = self.vrp + self.vpn * 0.5 * self.extent[2] 93 | t1 = get_translation_matrix(-vrc[0], -vrc[1], -vrc[2]) 94 | # Axis align 95 | r1 = get_rotation_matrix(self.u, self.vup, self.vpn).T 96 | # Rotate U and VUP 97 | r2 = y_rot(vupAngle) 98 | r3 = x_rot(uAngle) 99 | # Translate back 100 | t2 = get_translation_matrix(vrc[0], vrc[1], vrc[2]) 101 | 102 | # Get the rotation matrix and apply it to the axes 103 | rotate = reduce(np.dot, [t2, r1.T, r3, r2, r1, t1]) 104 | self.vrp = np.array(np.dot(rotate, np.append(self.vrp, [1])))[:3] 105 | self.u = normalize(np.array(np.dot(rotate, np.append(self.u, [0])))[:3]) 106 | self.vup = normalize(np.array(np.dot(rotate, np.append(self.vup, [0])))[:3]) 107 | self.vpn = normalize(np.array(np.dot(rotate, np.append(self.vpn, [0])))[:3]) 108 | 109 | 110 | 111 | 112 | # Normalize the first three coordinates of a vector, ignoring its homogeneous coordinate 113 | def normalize(vector, dim=None): 114 | if dim is None: 115 | dim = vector.size 116 | length = np.sqrt(np.sum(vector[:dim] * vector[:dim])) 117 | 118 | np.divide.at(vector, [i for i in range(dim)], length) 119 | return vector 120 | 121 | 122 | # Np array to translate by the provided values 123 | def get_translation_matrix(tx, ty, tz): 124 | return np.array([[1, 0, 0, tx], 125 | [0, 1, 0, ty], 126 | [0, 0, 1, tz], 127 | [0, 0, 0, 1]], dtype=float) 128 | 129 | # Np array to scale a vector by the provided coefficients 130 | def get_scale_matrix(dx, dy, dz): 131 | return np.array([[dx, 0, 0, 0], 132 | [0, dy, 0, 0], 133 | [0, 0, dz, 0], 134 | [0, 0, 0, 1]], dtype=float) 135 | 136 | # Np array to rotate to align with the given orthonormal basis 137 | def get_rotation_matrix(u, vup, vpn): 138 | return np.array([[u[0], u[1], u[2], 0], 139 | [vup[0], vup[1], vup[2], 0], 140 | [vpn[0], vpn[1], vpn[2], 0], 141 | [0, 0, 0, 1]], dtype=float) 142 | 143 | # Np array to rotate around the x axis by the given angle 144 | def x_rot(angle): 145 | c = math.cos(angle) 146 | s = math.sin(angle) 147 | return np.array([[1, 0, 0, 0], 148 | [0, c, -s, 0], 149 | [0, s, c, 0], 150 | [0, 0, 0, 1]], dtype=float) 151 | 152 | # Np array to rotate around the y axis by the given angle 153 | def y_rot(angle): 154 | c = math.cos(angle) 155 | s = math.sin(angle) 156 | return np.array([[c, 0, s, 0], 157 | [0, 1, 0, 0], 158 | [-s, 0, c, 0], 159 | [0, 0, 0, 1]], dtype=float) 160 | 161 | 162 | # Test 163 | if __name__ == '__main__': 164 | v = View() 165 | print(v.build()) 166 | print(np.dot(v.build(), np.array([0,0,0,1], dtype=float).T)) 167 | -------------------------------------------------------------------------------- /ui/objects.py: -------------------------------------------------------------------------------- 1 | import types 2 | from collections import OrderedDict 3 | from dataclasses import dataclass, asdict 4 | from enum import Enum 5 | from inspect import signature 6 | from typing import TypedDict, NamedTuple, Tuple 7 | 8 | import numpy as np 9 | import numexpr as ne 10 | import hallucinator as hl 11 | import tkinter as tk 12 | import math 13 | 14 | from ui import controls 15 | from ui.util import build_dataclass, get_param_info_func 16 | 17 | 18 | # TODO Add more 19 | class ColorStyle(Enum): 20 | GRAYSCALE = "Grayscale" 21 | HSV = "HSV" 22 | WHAT = "???" 23 | 24 | 25 | # TODO Add more 26 | class PlotStyle(Enum): 27 | CONTOUR = "Contour" 28 | REAL = "Real" 29 | IMAGINARY = "Imaginary" 30 | 31 | 32 | @dataclass 33 | class SceneSettings: 34 | # Don't end lines with commas 35 | # TODO dictionaries for heterogeneous styles and densities 36 | autorender: bool = True 37 | camera_position: Tuple[float, float, float] = (0, 0, 0) 38 | #camera_rotation: Tuple[float, float, float] = (0, 0, 0) 39 | densities: Tuple[int, int] = (10, 10) 40 | projection_type: hl.Projections = hl.Projections.WEAK 41 | styles: hl.Styles = hl.Styles.UNIFORM 42 | x_range: Tuple[int, int] = (-5, 5) 43 | y_range: Tuple[int, int] = (-5, 5) 44 | resolution: int = 100 45 | 46 | 47 | # NamedTuple not mutable 48 | # TypedDict no defaults 49 | # SimpleNamespace no signature 50 | # @dataclass is the way!!! 51 | @dataclass 52 | class ViewSettings: 53 | style: ColorStyle = ColorStyle.GRAYSCALE 54 | plot_type: PlotStyle = PlotStyle.CONTOUR 55 | value_range: Tuple[float, float] = (-1, 1) 56 | resolution: int = 500 57 | # example_complex: complex = 1 58 | render_all: bool = True 59 | autorender: bool = True 60 | 61 | 62 | class ComputedObject(NamedTuple): 63 | name: str 64 | func: types.FunctionType 65 | params: dataclass 66 | 67 | @classmethod 68 | def new(cls, name, func): 69 | param_defaults, param_types = get_param_info_func(func) 70 | # param_defaults.pop("view_settings") 71 | # param_types.pop("view_settings") # FIXME this architecture is wrong... 72 | DataClass = build_dataclass(name, param_defaults, param_types) 73 | return ComputedObject(name=name, func=func, params=DataClass()) 74 | 75 | def apply(self): 76 | return self.func(**asdict(self.params)) 77 | 78 | 79 | # TODO 80 | class DerivedObject: 81 | name: str 82 | operation: Enum 83 | operands: list 84 | 85 | 86 | def zone_plate(view_settings: ViewSettings, x: float = 0, y: float = 0): 87 | center = [x, y] 88 | xy = hl.xy_plane(value_range=view_settings.value_range, resolution=view_settings.resolution) 89 | x2y2 = ne.evaluate("sum(((xy-center)*10)**2, axis=2)") 90 | return hl.contour_image(x2y2, **asdict(view_settings)) 91 | 92 | 93 | def pinch_zone(view_settings: ViewSettings, x: float = 0, y: float = 0): 94 | center = [x, y] 95 | xy = hl.xy_plane(value_range=view_settings.value_range, resolution=view_settings.resolution) 96 | x2y2 = ne.evaluate("((xy-center)*10)**2") 97 | c = hl.as_complex(x2y2) 98 | pinch = ne.evaluate("c.real-c.imag") 99 | return hl.contour_image(pinch, **asdict(view_settings)) 100 | 101 | 102 | def spiral( 103 | location: Tuple[int, int, int] = (0, 0, 20), 104 | coil_density: float = 1, 105 | radius: float = 1, 106 | turns: float = 5, 107 | rotate_x: float = math.pi / 4): 108 | 109 | return hl.path_3(path_func=hl.gen_spiral(coil_density, radius), 110 | p_range=(0, turns), 111 | path_length=10 * math.pi).rotate(theta=rotate_x, 112 | axis=(1, 0, 0), 113 | p=location).translate(location) 114 | 115 | 116 | def surface(amplitude: float = 1, frequency: float = 1, 117 | direction: float = math.pi, 118 | phase: float = math.pi, 119 | rotate_x: float = 0, 120 | rotate_y: float = 0, 121 | rotate_z: float = 0, 122 | x: int = 0, 123 | y: int = 0, 124 | z: int = 40): 125 | surface_obj = hl.ParaObject3(hl.gen_plane_wave(amplitude, frequency, hl.unit_vector(direction), phase), 126 | region_type='2d', 127 | region_params={'surface_range': ((-5, 5), (-5, 5))}, 128 | species='surface') 129 | surface_obj = surface_obj.rotate(theta=rotate_x, axis=(1, 0, 0)) 130 | surface_obj = surface_obj.rotate(theta=rotate_y, axis=(0, 1, 0)) 131 | surface_obj = surface_obj.rotate(theta=rotate_z, axis=(0, 0, 1)) 132 | surface_obj = surface_obj.translate((x, y, z)) 133 | return surface_obj 134 | 135 | 136 | available_objects = { 137 | # "Zone plate": zone_plate, 138 | # "Pinch zone": pinch_zone, 139 | "Surface": surface, 140 | #"Spiral": spiral, 141 | } 142 | 143 | # self.name = ("Type", string) 144 | # self.x = ("x", float, 0) 145 | # 146 | # 147 | # 148 | # 149 | # checkbox 150 | # dropdown 151 | # textfield 152 | # slider 153 | # 154 | # bool: checkbox 155 | # enum: dropdown 156 | # int, floats: input field, slider? 157 | # string: input field 158 | # 159 | # 160 | # available_shapes = [ 161 | # zoneplate, 162 | # pinchzone, 163 | # ] 164 | # 165 | # class View: 166 | # range 167 | # x, y 168 | # style 169 | # 170 | # class Shape: 171 | # name 172 | # func 173 | # params = {} 174 | # 175 | # class Derived: 176 | # name 177 | # operation 178 | # operands 179 | 180 | 181 | # 182 | # class Shape: 183 | # type, func, name, x, y, scale, conjugate, rotate 184 | # 185 | # 186 | # class FuncOfR(Shape): 187 | # function_string 188 | # 189 | # def render(): 190 | # plane = ne.evaluate("...") 191 | # return ... 192 | # 193 | # class PinchZone(Shape): 194 | # self.func = zoneplate 195 | # 196 | # def render(): 197 | # plane = ne.evaluate("...") 198 | # return ... 199 | # 200 | # func(**params) 201 | # 202 | # 203 | # def zoneplate(..., **kwargs): 204 | # 205 | # zoneplate(example=1): 206 | # 207 | # 208 | # class Ellipsoid(Shape): 209 | # a, b = 1 210 | # 211 | # class Sphere(FuncOfR): 212 | # func: 213 | # 214 | # class Fresnel(Zoneplate): 215 | # distance, wavelength=1, 216 | # 217 | # 218 | # class Zoneplate(Shape):distance 219 | # 220 | # 221 | # 222 | # shapes=[ 223 | # { name, func, params={} } 224 | # ] 225 | # 226 | # class Compound(Shape): 227 | # operation_type: [ 228 | # operands: [name1, name2, name3] 229 | # name=(A+B) 230 | # 231 | # (A+B)*C 232 | -------------------------------------------------------------------------------- /ui/static/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/ui/static/__init__.py -------------------------------------------------------------------------------- /ui/static/close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/socketteer/hallucinator/87705406004fc88850b760e1322e6aedef927fa7/ui/static/close.gif -------------------------------------------------------------------------------- /ui/tks.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk, font, filedialog 3 | import csv 4 | from ui import controls 5 | from ui.deprecated import wizards 6 | 7 | # Class to create a basic dialog pop-up box. Designed for extension. 8 | # From http://effbot.org/tkinterbook/tkinter-dialog-windows.htm 9 | class Dialog(tk.Toplevel): 10 | def __init__(self, parent, title=None): 11 | tk.Toplevel.__init__(self, parent) 12 | self.transient(parent) 13 | self.wm_resizable(height=False, width=False) 14 | 15 | if title: 16 | self.title(title) 17 | 18 | self.parent = parent 19 | self.result = None 20 | body = tk.Frame(self) 21 | self.initial_focus = self.body(body) 22 | body.pack(padx=5, pady=5) 23 | 24 | self.buttonbox() 25 | self.grab_set() 26 | if not self.initial_focus: 27 | self.initial_focus = self 28 | self.protocol("WM_DELETE_WINDOW", self.cancel) 29 | self.geometry("+%d+%d" % (parent.winfo_rootx() + 50, 30 | parent.winfo_rooty() + 50)) 31 | self.initial_focus.focus_set() 32 | 33 | self.wait_window(self) 34 | 35 | # construction hooks 36 | def body(self, master): 37 | # create dialog body. return widget that should have 38 | # initial focus. this method should be overridden 39 | pass 40 | 41 | def buttonbox(self): 42 | # add standard button box. override if you don't want the standard buttons 43 | box = tk.Frame(self) 44 | 45 | w = tk.Button(box, text="OK", width=10, command=self.ok, default=tk.ACTIVE) 46 | w.pack(side=tk.LEFT, padx=5, pady=5) 47 | w = tk.Button(box, text="Cancel", width=10, command=self.cancel) 48 | w.pack(side=tk.LEFT, padx=5, pady=5) 49 | 50 | self.bind("", self.ok) 51 | self.bind("", self.cancel) 52 | box.pack() 53 | 54 | # standard button semantics 55 | def ok(self, event=None): 56 | if not self.validate(): 57 | self.initial_focus.focus_set() # put focus back 58 | return 59 | self.withdraw() 60 | self.update_idletasks() 61 | self.apply() 62 | self.cancel() 63 | 64 | def cancel(self, event=None): 65 | # put focus back to the parent window 66 | self.parent.focus_set() 67 | self.destroy() 68 | 69 | # command hooks 70 | def validate(self): 71 | return 1 # override 72 | 73 | def apply(self): 74 | pass # override 75 | 76 | 77 | class SelectorDialog(Dialog): 78 | 79 | def __init__(self, parent, title, choices, callback): 80 | self.choices = choices 81 | self.callback = callback 82 | print("Did I do it?") 83 | super().__init__(parent, title) 84 | 85 | def body(self, master): 86 | # Build the listbox 87 | height = min(15, max(4, len(self.choices))) 88 | self.listBox = tk.Listbox(master, width=40, height=height, selectmode="SINGLE") 89 | for choice in self.choices: 90 | self.listBox.insert(tk.END, choice) 91 | self.listBox.grid(row=1, columnspan=4) 92 | 93 | def validate(self): 94 | return len(self.listBox.curselection()) > 0 95 | 96 | def apply(self): 97 | for idx in self.listBox.curselection(): 98 | self.callback(self.choices[idx]) 99 | 100 | 101 | # PUT THINGS INSIDE frame.scrollable_frame 102 | # FIXME FIXME FIXME Not doing this will cause unexplainable hangups 103 | class ScrollableFrame(ttk.Frame): 104 | def __init__(self, container, *args, **kwargs): 105 | super().__init__(container, *args, **kwargs) 106 | canvas = tk.Canvas(self, height=300) # FIXME this isn't the way to do this 107 | scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview) 108 | self.scrollable_frame = ttk.Frame(canvas) 109 | 110 | self.scrollable_frame.bind( 111 | "", 112 | lambda e: canvas.configure( 113 | scrollregion=canvas.bbox("all") 114 | ) 115 | ) 116 | 117 | canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") 118 | 119 | canvas.configure(yscrollcommand=scrollbar.set) 120 | 121 | canvas.pack(side="left", fill="both", expand=True) 122 | scrollbar.pack(side="right", fill="y") 123 | -------------------------------------------------------------------------------- /ui/util.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, asdict, field, make_dataclass, fields 2 | from enum import Enum 3 | from inspect import signature 4 | from pprint import pprint 5 | from typing import Tuple, get_args, get_origin 6 | 7 | import numpy as np 8 | import tkinter as tk 9 | 10 | 11 | # Returns {param:default} and {param:type} for the params of a function or data structure 12 | from ui import controls 13 | def get_param_info_func(func): 14 | return {name: param.default for name, param in signature(func).parameters.items()}, \ 15 | {name: param.annotation for name, param in signature(func).parameters.items()} 16 | 17 | 18 | def get_param_info_dataclass(DataClass): 19 | return {field.name: field.default for field in fields(DataClass)}, \ 20 | {field.name: field.type for field in fields(DataClass)}, 21 | 22 | 23 | generic_types = { 24 | bool: (bool, np.bool), 25 | int: (int, np.integer), 26 | float: (float, np.floating), 27 | complex: (complex, np.complexfloating), 28 | str: (str, np.str), 29 | list: (tuple, list, np.ndarray), 30 | Enum: (Enum,) 31 | } 32 | # Returns the builtin type corresponding to the given type. If it is a list, it also returns the types in the list 33 | def convert_to_builtin(type_to_convert): 34 | # If something like typing.Tuple[int, int], change it to Tuple 35 | subtypes = None 36 | if get_origin(type_to_convert) is not None: 37 | subtypes = get_args(type_to_convert) 38 | type_to_convert = get_origin(type_to_convert) 39 | 40 | for builtin_type, corresponding_types in generic_types.items(): 41 | if issubclass(type_to_convert, corresponding_types): 42 | return builtin_type, subtypes 43 | 44 | raise ValueError(f"No builtin for {type_to_convert}") 45 | 46 | 47 | def build_dataclass(name, param_defaults, param_annotations): 48 | dataclass_params = [ 49 | (name, param_annotations[name], field(default=param_defaults[name])) 50 | for name in param_defaults.keys() 51 | ] 52 | return make_dataclass(name, dataclass_params) 53 | 54 | 55 | def build_dataclass_for_function(func, name=None): 56 | param_defaults, param_annotations = get_param_info_func(func) 57 | if name is None: 58 | name = func.__name__.title() 59 | DataClass = build_dataclass(name, param_defaults, param_annotations) 60 | 61 | return DataClass() 62 | 63 | 64 | 65 | def test(): 66 | def test_func(a: int = 1, b: str = "Test", c: bool = False): 67 | print(a, b, c) 68 | 69 | dc = build_dataclass_for_function(test_func) 70 | print(dc) 71 | print(test_func(**asdict(dc))) 72 | pprint(get_param_info_dataclass(dc)) 73 | 74 | 75 | if __name__ == "__main__": 76 | test() 77 | 78 | 79 | # tkinter_types = { 80 | # int: tk.IntVar, 81 | # float: tk.DoubleVar, 82 | # complex: tk.DoubleVar, 83 | # bool: (bool, np.bool), 84 | # Enum: tk.StringVar, 85 | # } 86 | # 87 | # input_types = { 88 | # ((float, np.floating), "spinbox"), # Includes float32,64 89 | # ((complex, np.complexfloating), "spinbox"), # Includes complex64,128 90 | # ((int, np.integer), "spinbox"), # Includes uints 91 | # (Enum, "dropdown"), 92 | # ((bool, np.bool), "checkbox") 93 | # } 94 | 95 | 96 | 97 | --------------------------------------------------------------------------------