{"id":29196143,"url":"https://github.com/agarnung/threepde","last_synced_at":"2025-07-02T06:02:40.453Z","repository":{"id":300476765,"uuid":"1006272388","full_name":"agarnung/threepde","owner":"agarnung","description":"A 3D visualization of image evolution under partial differential equations","archived":false,"fork":false,"pushed_at":"2025-06-30T22:41:02.000Z","size":2273,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-30T23:30:32.119Z","etag":null,"topics":["computer-vision","image-processing","partial-differential-equations","pde","three-js","threejs"],"latest_commit_sha":null,"homepage":"https://agarnung.github.io/threepde/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agarnung.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-21T22:04:10.000Z","updated_at":"2025-06-30T22:41:05.000Z","dependencies_parsed_at":"2025-06-21T23:37:42.467Z","dependency_job_id":null,"html_url":"https://github.com/agarnung/threepde","commit_stats":null,"previous_names":["agarnung/threepde"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/agarnung/threepde","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agarnung%2Fthreepde","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agarnung%2Fthreepde/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agarnung%2Fthreepde/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agarnung%2Fthreepde/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agarnung","download_url":"https://codeload.github.com/agarnung/threepde/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agarnung%2Fthreepde/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263083490,"owners_count":23411160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["computer-vision","image-processing","partial-differential-equations","pde","three-js","threejs"],"created_at":"2025-07-02T06:00:54.904Z","updated_at":"2025-07-02T06:02:40.429Z","avatar_url":"https://github.com/agarnung.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# threepde\n\nA 3D visualization of image evolution under partial differential equations\n\n[![Deploy to GitHub Pages](https://github.com/agarnung/threepde/actions/workflows/deploy.yml/badge.svg?branch=main\u0026event=page_build)](https://github.com/agarnung/threepde/actions/workflows/deploy.yml)\n\n[![](https://img.shields.io/badge/Three.js%20Forum-Visit-blue?style=for-the-badge)](https://discourse.threejs.org/t/3d-image-evolution-via-partial-differential-equations/84357)\n\nWant to know more about this field? Check many more interesting PDEs applied to images: [image-inpainting-app](https://www.researchgate.net/publication/387140474_Physics_Meets_Pixels_PDE_Models_in_Image_Processing/stats) and [Physics Meets Pixels: PDE Models in Image Processing](https://arxiv.org/abs/2412.11946).\n\nA few captures:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./assets/1.png\" alt=\"Imagen 1\" width=\"600\" /\u003e\n  \u003cbr /\u003e\n  \u003cimg src=\"./assets/2.png\" alt=\"Imagen 2\" width=\"600\" /\u003e\n  \u003cbr /\u003e\n  \u003cimg src=\"./assets/3.png\" alt=\"Imagen 3\" width=\"600\" /\u003e\n\u003c/p\u003e\n\n# Usage\n\n**Basic Interaction**\n- Upload your own images (WebP-friendly formats)\n- Toggle mesh/wireframe visibility\n- Adjust simulation speed with slider\n- Save current image\n\n**Main Options**\n- **Color maps**:\n  - `constant` (uniform color)\n  - `graymap` (grayscale)\n  - `constant-color` (original RGB)\n  - `constant-chrominance` (height L + original ab)\n  - Presets: `jet`, `viridis`, `inferno`, `seismic`, `RdYlBu`\n\n- **Available PDEs**:\n  - Heat equation\n  - Wave equation\n  - Exponential decay (ODE)\n\n- **Boundary conditions**:\n  - Dirichlet (fixed value)\n  - Neumann (fixed derivative)\n  - Periodic\n  - Robin (mixed)\n  - Special cases\n\n**Keyboard Shortcuts**\n| Key  | Action                |\n|------|-----------------------|\n| E    | Toggle 3D fullscreen  |\n| R    | Run/Pause simulation  |\n| S    | Save current image    |\n| N    | Toggle normalization  |\n| ⟳    | Reset simulation      |\n\n**Basic Usage**\n1. Select image (or use default)\n2. Configure parameters:\n   - PDE type\n   - Boundary condition\n   - Color map\n   - ...\n3. Enable simulation with `Run` checkbox or press (R)\n4. Enjoy solving the PDE in your image \n\n# Technical notes about actions\n\nThe input images must be WebP format, since RGBA is read. Converting a monochrome image to WebP and then passing it to the web is valid.\n\nThe project folder structure is designed to be modular, optimized, and separate responsibilities, e.g., static web parts from public or asset files, and from CI/CD components:\n\n```txt\nalejandro@DESKTOP-AIFFN1L:/opt/threepde$ tree\n.\n├── LICENSE\n├── XXXX-XX-XX-three-PDE.md\n├── index.html\n├── public\n│   ├── css\n│   │   └── styles.css\n│   ├── favicon.ico\n│   └── images\n│       ├── lena_gray.webp\n│       └── lena_rgb.webp\n└── src\n    ├── helpers\n    │   ├── color-maps.js\n    │   ├── image-mesh-converter.js\n    │   └── image-preprocessor.js\n    ├── main.js\n    └── solver.js\n```\n\nDuring development, only files inside src/ are modified. New images (by default, ones the user can select with a selector) are added to assets/images/ and are immediately accessible by the client in their original format without transfer processing. To preview the site during development, you can use [_Live Server_](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) or [_Live Preview_](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) in VSCode (accessible at http://localhost:5500), which serves index.html locally and reloads automatically when saving changes; thus, no backend (Flask, etc.) is required since everything served is static.\n\nWe use Three.js via CDN for both development and production, specified in index.html. This avoids duplicates in the repository, leverages browser cache, and uses a minified, optimized version of the library.\n\n```html\n\u003c!-- In index.html (ALWAYS use CDN) --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js\"\u003e\u003c/script\u003e\n```\n\nWe use GitHub Actions to detect pushes to main and automatically minify all JS files in src/ to assets/js/main.min.js — i.e., to minify and deploy the website. This keeps the src/ code intact and only uploads the final optimized version to GitHub Pages, where it is served. This project is served via GitHub Pages, which delivers only static content (HTML, CSS, JS) with no backend required. In contrast, platforms like PythonAnywhere use Flask to run Python servers generating dynamic content. That’s why no backend is needed or configured here.\n\nWe should commit our entire src/, index.html, public/ and the YAML (workflow) file, to keep track of them, but not the dist/, because this is a temporary directvory were we copy the \"ready-to-publish\" files (we create a custom repo PAT with `workflow` permisson enabled).\n\n```yaml\nname: Deploy to GitHub Pages\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install dependencies\n        run: npm install -g terser\n\n      - name: Build and Minify\n        run: |\n          mkdir -p dist/assets/js\n          mkdir -p dist/assets/css\n          cp index.html dist/\n          cp -r public/images dist/images\n          cp public/favicon.ico dist/\n          \n          # Minify JS\n          npx terser \"src/**/*.js\" -c -m -o dist/assets/js/main.min.js\n\n          # Minify CSS\n          npx cleancss -o dist/assets/css/styles.min.css public/css/styles.css\n\n      - name: Deploy to GitHub Pages\n        uses: peaceiris/actions-gh-pages@v3\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: dist\n```\n\nTo decide which JS scripts to load from index.html, a conditional loading block based on the current URL could be created, to differentiate whether to load our source code in development or the minified bundle in production:\n\n```html\n\u003c!-- Conditional loading based on URL --\u003e\n\u003cscript\u003e\n  if (window.location.hostname === 'localhost') {\n    // Development: load individual modules\n    document.write('\u003cscript src=\"src/image-processor.js\"\u003e\u003c\\/script\u003e');\n    document.write('\u003cscript src=\"src/pde-simulator.js\"\u003e\u003c\\/script\u003e');\n  } else {\n    // Production: load minified bundle\n    const script = document.createElement('script');\n    script.src = 'assets/js/main.min.js';\n    script.defer = true;\n    document.head.appendChild(script);\n  }\n\u003c/script\u003e\n```\n\nThus, on localhost we develop with individual source files, and on our domain (or here on github.io) the [minified bundle ](https://diegobersano.wordpress.com/2014/05/21/bundles-minificacion-y-cdn-en-asp-net-mvc/)is used automatically.\n\nThere are two types of images in the project:\n\n1. Static images, predefined by me in public/images/, which are part of the repository and must be optimized. For example, tools like ImageMagick or squoosh-cli should be used to convert them to an optimized format (e.g., WebP) before uploading, to reduce repository size, improve initial load time, and reduce bandwidth:\n\n```bash\nconvert lena.png -resize 512x512 -quality 99 public/images/lena.webp\n\nnpx @squoosh/cli --webp '{quality:99}' lena.png -d public/images/\n```\n\n2. Images uploaded by the user, coming from their local system, processed immediately and entirely in the browser. The flow is as follows:\n\n```txt\nThe User selects an image in the Browser.\n\nThe browser uses the FileReader API (JavaScript) to read the image file.\n\nJavaScript then draws the image onto an HTML Canvas.\n\nFrom the canvas, JavaScript gets the image pixel data using getImageData().\n\nThe pixel data is normalized to values between 0 and 1.\n\nJavaScript uses Three.js to create a heightmap based on the normalized data.\n\nFinally, WebGL renders the heightmap visually.\n```\n\nFor curiosity, the complete development and production workflow would be:\n\n```txt\nEdit files in the src/ folder.\n\nPreview changes using a Live Server.\n\nCheck if it works correctly:\n\n    If yes, push to GitHub.\n\n    If no, go back to editing files.\n\nGitHub Actions minify and deploy the project automatically.\n\nThe site becomes available at user.github.io/repo.\n```\n\nAdditionally, we use [MathJax](\u003chttps://docs.mathjax.org/en/stable/start.html#using-the-mathjax-content-delivery-network-cdn)[Jax](https://stackoverflow.com/questions/49300667/which-mathjax-cdn-script-should-be-used\u003e) (v3) to write equations, although MathML (native to HTML5) could also be used, but it is less readable and does not support LaTeX. A polyfill is also used to guarantee that some ES6 JavaScript features are available on older browsers that do not support them natively.\n\nThe ES6 version (three.module.js) is used with an import map, and not the UMD (three.min.js), as it is the [recommended modern version](https://discourse.threejs.org/t/umd-version-of-three-js-v-161/60912).\n\nWe also use Stats.js (from CDN) to display FPS.\n\n\u003e [!NOTE]\n\u003e Whatever the input image type, PDEs are solved using the luminance channel (even if RGB values are shown on top; the evolution is governed by the monochrome intensity here).\n\nTo learn more about boundary conditions: https://en.wikipedia.org/wiki/Boundary_value_problem#Examples. And check more discretization schemes: [forward Euler, backward Euler, Crank-Nicholson, forward-backward Euler, etc.](https://en.wikipedia.org/wiki/Explicit_and_implicit_methods).\n\nTo be precise, exponential decay is an ODE, not a PDE, so there is no spatial propagation and no CFL condition is required, but it does have a stability condition. \n\nIn constant-color mode (useOriginalRGB = true), the RGB colors of the image remain completely unchanged — this is like placing the original image as a mantle over the mesh. The surface has no color evolution; the image looks intact. It no longer truly behaves like a heightmap, since the mesh is essentially just being draped with the image as a texture. All heightmap cells share the same color, which looks uniform and gray but is preserved to emphasize colormap functionality. Only in this mode does the 2D canvas display the image at original resolution because the solver does not alter intensity pixel-by-pixel. Other modes downsample images to 512×512 to optimize solver performance.\n\nIn contrast, constant-chrominance mode (useOriginalRGB = false) combines the evolving height (L, from lightness or luminance) with the original chrominance (a, b channels from LAB color space). Here, the color changes over time according to the height evolution, but the original chrominance is preserved. This mode gives a more physically informed visual feedback of the PDE evolution, while maintaining some of the original image’s color identity.\n\n# References\n\n- Inspired by the simulation from https://github.com/chrismars91/fdm?tab=readme-ov-file\n\n- 3 options to use THREE.js in our project: https://discourse.threejs.org/t/help-getting-set-up-with-vscode-and-three-js/19606.\n\n# TODO\n\n- Regarding the variable needToUpdate: TODO run the solver in another thread and manage a shared variable with a mutex to notify when computation finishes, so rendering continues using the current state until it finishes, avoiding blocking rendering if the solver step is not complete yet. This should be done with a Web Worker, but it is challenging.\n\n- Allow real-time control of roughness and metalness of the mesh material (see createHeightMesh()). Also allow switching the material type (basic or standard, etc.) in a dedicated Three.js options panel.\n\n- Add a paragraph about the original \"lab mode\". Explain that in \"mantle mode\" the image remains intact (meaning \"mantle mode\" really means \"keep original colors\" mode, while the non-mantle mode means \"keep original chrominance\" mode).\n\n- Allow users to select the heightmap size? Either by specifying width and height or by width or height while maintaining aspect ratio (not forced 512x512).\n\n- Fix bundle and minification to serve `gh-pages` instead of `main` branch.\n\n- Habilitar como GPU ON/OFF para algunas sino todas las PDE (i.e. computarlas en shaders). Inspirarse en: https://chrisboligprojects.pythonanywhere.com/vertexWaves.\n\n- Para interpolar valores de color de la imagen a la malla, usar [perspective-correct interpolation](https://stackoverflow.com/questions/17537879/in-webgl-what-are-the-differences-between-an-attribute-a-uniform-and-a-varying).\n\n- Implementar opción de alternar la resolución llevándola a GPU. No debería convertirse a ImageData en cada step, pues esto es el cuello de botella que ping-pong (heat eq.) o buffer circular (wave eq.) trataría de resolver (alta carga de transferencia CPU-GPU), sino que lo óptimo es, si se está en GPU, renderizar directamente la textura en pantalla usando un quad de pantalla completa (sceneView), por ejemplo.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagarnung%2Fthreepde","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagarnung%2Fthreepde","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagarnung%2Fthreepde/lists"}