From f840bd6d2a4e1a4fa84fa81c97079f4c21138bc9 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Tue, 30 Sep 2025 22:58:07 -0400 Subject: [PATCH 1/8] remove unecessary calls to swiper.update() --- photomap/frontend/static/javascript/grid-view.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/photomap/frontend/static/javascript/grid-view.js b/photomap/frontend/static/javascript/grid-view.js index c504e240..3a96f244 100644 --- a/photomap/frontend/static/javascript/grid-view.js +++ b/photomap/frontend/static/javascript/grid-view.js @@ -247,7 +247,6 @@ function addGridEventListeners() { // onChange event state.swiper.on("slideChange", async () => { // If the currently highlighted slide s.seais not visible, move the highlight to the top-left slide - await state.swiper.update(); // Ensure Swiper state is current const currentSlide = slideState.getCurrentSlide(); const currentGlobal = currentSlide.globalIndex; const slideEl = document.querySelector( @@ -345,16 +344,7 @@ async function resetAllSlides() { true; console.warn("removeAllSlides failed:", err); } - try { - state.swiper.update(); // ensure internal state is correct - } catch (err) { - console.warn( - "swiper.update() failed:", - err, - "\ncontinuing anyway, slide length =", - state.swiper.slides.length - ); - } + await loadBatch(targetIndex, true); await loadBatch(targetIndex + slidesPerBatch, true); // Load two batches to start in order to enable forward navigation if (targetIndex > 0) { @@ -569,9 +559,6 @@ function enforceHighWaterMark(trimFromEnd = false) { delete slideData[g]; } - // Update Swiper internals - state.swiper.update(); - // Adjust active index once to avoid a jump: if (!trimFromEnd) { // We removed removeScreens full screens from the start. @@ -586,7 +573,6 @@ function enforceHighWaterMark(trimFromEnd = false) { state.swiper.slideTo(targetActive, 0); } - state.swiper.update(); } function setupContinuousNavigation() { From 04d1d68f6a9841a88f4143f9ff1c70dd39a7bebe Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 00:52:35 -0400 Subject: [PATCH 2/8] use thumbnails for grid --- photomap/frontend/static/javascript/grid-view.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/photomap/frontend/static/javascript/grid-view.js b/photomap/frontend/static/javascript/grid-view.js index 3a96f244..cb7d2ced 100644 --- a/photomap/frontend/static/javascript/grid-view.js +++ b/photomap/frontend/static/javascript/grid-view.js @@ -206,6 +206,7 @@ function addGridEventListeners() { // Load more when reaching the end state.swiper.on("slideNextTransitionStart", async () => { if (state.isTransitioning) return; // Don't load more during transitions + showSpinner(); state.isTransitioning = true; await waitForBatchLoadingToFinish(); state.isTransitioning = false; @@ -224,6 +225,7 @@ function addGridEventListeners() { : lastSlideIndex + 1; await loadBatch(index, true); // Append a batch at the end } + hideSpinner(); }); // Load more when reaching the start @@ -393,7 +395,6 @@ async function loadBatch(startIndex = null, append = true) { const globalIndex = slideState.indexToGlobal(offset); // In the event that the slide is already loaded, skip it. - // I'm not sure this logic is necessary if load tracking is done correctly. if (loadedImageIndices.has(globalIndex)) { continue; } @@ -437,7 +438,6 @@ async function loadBatch(startIndex = null, append = true) { // --- PREPEND LOGIC: Add a full screen's worth of slides before startIndex --- for (let i = 0; i < slidesPerBatch; i++) { const globalIndex = slideState.indexToGlobal(startIndex - i - 1); // reverse order - // not sure this is wanted here if (loadedImageIndices.has(globalIndex)) continue; try { @@ -746,13 +746,15 @@ function makeSlideHTML(data, globalIndex) { data.searchIndex = slideState.globalToSearch(globalIndex); slideData[globalIndex] = data; // Cache the data + // replace image_url with thumbnail_url + const thumbnail_url = `thumbnails/${state.album}/${globalIndex}?size=${slideHeight}`; return `
- ${data.filename}
`; From 58e9a10e8ee71b42d11d0c1687a8c40c5378e119 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 04:00:39 -0400 Subject: [PATCH 3/8] fix some race conditions --- .../frontend/static/javascript/grid-view.js | 36 +++++++++---------- .../frontend/static/javascript/slide-state.js | 1 - photomap/frontend/static/javascript/swiper.js | 3 ++ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/photomap/frontend/static/javascript/grid-view.js b/photomap/frontend/static/javascript/grid-view.js index cb7d2ced..ddc436f5 100644 --- a/photomap/frontend/static/javascript/grid-view.js +++ b/photomap/frontend/static/javascript/grid-view.js @@ -21,6 +21,7 @@ let slidesPerBatch = 0; // Number of slides to load per batch let slideHeight = 140; // Default slide height (reduced from 200) let currentRows = 0; // Track current grid dimensions let currentColumns = 0; +let suppressSlideChange = false; let slideData = {}; // Store data for each slide const GRID_MAX_SCREENS = 6; // Keep up to this many screens in memory (tweakable) @@ -120,11 +121,6 @@ export async function initializeGridSwiper() { const swiperContainer = document.querySelector(".swiper"); swiperContainer.classList.add("grid-mode"); - state.swiper.on("slideChange", () => { - // do nothing with this. - return; - }); - addGridEventListeners(); setupContinuousNavigation(); setupGridResizeHandler(); @@ -246,9 +242,16 @@ function addGridEventListeners() { } }); + // transitionEnd event + state.swiper.on("transitionEnd", () => { + suppressSlideChange = false; + }); + // onChange event state.swiper.on("slideChange", async () => { - // If the currently highlighted slide s.seais not visible, move the highlight to the top-left slide + if (suppressSlideChange) return; + + // If the currently highlighted slide is not visible, move the highlight to the top-left slide const currentSlide = slideState.getCurrentSlide(); const currentGlobal = currentSlide.globalIndex; const slideEl = document.querySelector( @@ -294,7 +297,7 @@ function addGridEventListeners() { window.handleGridSlideDblClick = async function (globalIndex) { // Prevent navigation if we're already transitioning if (state.isTransitioning) return; - + slideState.setCurrentIndex(globalIndex, false); updateCurrentSlideHighlight(globalIndex); @@ -324,7 +327,6 @@ function addDoubleTapHandler(slideEl, globalIndex) { // @param {number|null} targetIndex - Optional index to include in first screen. // If null, use current slide index. async function resetAllSlides() { - if (!gridInitialized) return; if (!state.swiper) return; showSpinner(); @@ -412,10 +414,6 @@ async function loadBatch(startIndex = null, append = true) { console.error("Failed to load image:", error); break; } - if (i % 8 === 0) { - await new Promise(requestAnimationFrame); - if (state.isTransitioning) return; - } } if (slides.length > 0) state.swiper.appendSlide(slides); @@ -451,12 +449,10 @@ async function loadBatch(startIndex = null, append = true) { console.error("Failed to load image (prepend):", error); continue; } - if (i % 8 === 0) { - await new Promise(requestAnimationFrame); - if (state.isTransitioning) return; - } } if (slides.length > 0) { + suppressSlideChange = true; + state.swiper.prependSlide(slides); // After prepending slides, add double-tap handlers to all the new ones. @@ -468,7 +464,6 @@ async function loadBatch(startIndex = null, append = true) { } } state.swiper.slideTo(currentColumns, 0); // maintain current view - // enforce high water mark after prepending (trim the other side) enforceHighWaterMark(true); } } @@ -572,7 +567,6 @@ function enforceHighWaterMark(trimFromEnd = false) { const targetActive = Math.min(prevActive, maxActive); state.swiper.slideTo(targetActive, 0); } - } function setupContinuousNavigation() { @@ -689,10 +683,12 @@ function setupGridResizeHandler() { newGeometry.columns !== currentColumns || Math.abs(newGeometry.tileSize - slideHeight) > 10 ) { + // Current global index + const currentGlobalIndex = slideState.getCurrentSlide().globalIndex; // Reinitialize the grid completely await initializeGridSwiper(); - await loadBatch(); - await loadBatch(); // Load two batches to start + await loadBatch(currentGlobalIndex); + await loadBatch(currentGlobalIndex + slidesPerBatch); // Load two batches to start } }, 300); // 300ms debounce delay } diff --git a/photomap/frontend/static/javascript/slide-state.js b/photomap/frontend/static/javascript/slide-state.js index d434363c..6fd6e700 100644 --- a/photomap/frontend/static/javascript/slide-state.js +++ b/photomap/frontend/static/javascript/slide-state.js @@ -261,7 +261,6 @@ class SlideStateManager { } seekToSlideIndex() { - console.log("Sending seekToSlideIndex event") const slideInfo = this.getCurrentSlide(); window.dispatchEvent( new CustomEvent("seekToSlideIndex", { diff --git a/photomap/frontend/static/javascript/swiper.js b/photomap/frontend/static/javascript/swiper.js index 5b645b1e..bb0cc3fa 100644 --- a/photomap/frontend/static/javascript/swiper.js +++ b/photomap/frontend/static/javascript/swiper.js @@ -422,12 +422,15 @@ export async function resetAllSlides() { await waitForBatchLoadingToFinish(); setBatchLoading(true); + console.log("Resetting all slides in swiper"); + const slideShowRunning = state.swiper?.autoplay?.running; pauseSlideshow(); state.swiper.removeAllSlides(); const { globalIndex, searchIndex } = slideState.getCurrentSlide(); + console.log("Current slide index:", globalIndex, searchIndex); // Prevent intermediate rendering while we add slides const swiperContainer = document.querySelector(".swiper"); From 050881eb2c38812d3ad52681212810ad944d9e25 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 22:13:42 -0400 Subject: [PATCH 4/8] add more guards against running loadBatch() concurrently --- macos/setup.py | 72 ------------------- photomap/frontend/static/javascript/events.js | 1 - .../frontend/static/javascript/grid-view.js | 53 +++++++++++--- photomap/frontend/static/javascript/swiper.js | 2 + 4 files changed, 46 insertions(+), 82 deletions(-) delete mode 100644 macos/setup.py diff --git a/macos/setup.py b/macos/setup.py deleted file mode 100644 index 15c42532..00000000 --- a/macos/setup.py +++ /dev/null @@ -1,72 +0,0 @@ -from setuptools import find_packages, setup - -APP = ["../photomap/backend/photomap_outer_loop.py"] -DATA_FILES = [] -OPTIONS = { - "argv_emulation": True, - "packages": ["photomap"], - "includes": [ - "fastapi", - "uvicorn", - "jinja2", - "numpy", - "pandas", - "networkx", - "pillow_heif", - "platformdirs", - "pydantic", - "python_multipart", - "PyYAML", - "requests", - "scikit_learn", - "torch", - "tqdm", - "umap_learn", - "psutil", - "packaging", - "dash", - "clip_anytorch", - "colorama", - ], - "iconfile": "../photomap/frontend/static/icons/icon.icns", -} - -setup( - app=APP, - name="PhotoMapAI", - version="0.9.3", - description="AI-based image clustering and exploration tool", - author="Lincoln Stein", - author_email="lincoln.stein@gmail.com", - url="https://github.com/lstein/PhotoMapAI", - python_requires=">=3.10, <3.14", - packages=find_packages(include=["photomap", "photomap.*"]), - include_package_data=True, - install_requires=[ - "Pillow", - "clip-anytorch", - "colorama", - "dash", - "fastapi", - "jinja2", - "networkx", - "numpy", - "pandas", - "pillow-heif", - "platformdirs", - "pydantic", - "python-multipart", - "PyYAML", - "requests", - "scikit-learn", - "setuptools<67", - "torch", - "tqdm", - "umap-learn", - "uvicorn", - "psutil", - "packaging", - ], - options={"py2app": OPTIONS}, - setup_requires=["py2app"], -) diff --git a/photomap/frontend/static/javascript/events.js b/photomap/frontend/static/javascript/events.js index f6d29e35..2e0602ed 100644 --- a/photomap/frontend/static/javascript/events.js +++ b/photomap/frontend/static/javascript/events.js @@ -488,7 +488,6 @@ export async function toggleGridSwiperView(gridView = null) { else state.gridViewActive = gridView; saveSettingsToLocalStorage(); - const swiperContainer = document.querySelector(".swiper"); const gridViewBtn = document.getElementById("gridViewBtn"); const gridViewIcon = gridViewBtn.querySelector("svg"); diff --git a/photomap/frontend/static/javascript/grid-view.js b/photomap/frontend/static/javascript/grid-view.js index ddc436f5..049c9252 100644 --- a/photomap/frontend/static/javascript/grid-view.js +++ b/photomap/frontend/static/javascript/grid-view.js @@ -64,6 +64,7 @@ function calculateGridGeometry() { // Initialization code export async function initializeGridSwiper() { + console.log("Initializing grid swiper..."); gridInitialized = false; showSpinner(); eventRegistry.removeAll("grid"); // Clear previous event handlers @@ -219,7 +220,15 @@ function addGridEventListeners() { const index = slideState.isSearchMode ? slideState.globalToSearch(lastSlideIndex) + 1 : lastSlideIndex + 1; - await loadBatch(index, true); // Append a batch at the end + await waitForBatchLoadingToFinish(); + setBatchLoading(true); + try { + await loadBatch(index, true); // Append a batch at the end + } catch (error) { + console.log(error); + } finally { + setBatchLoading(false); + } } hideSpinner(); }); @@ -229,6 +238,7 @@ function addGridEventListeners() { if (state.isTransitioning) return; // Don't load more during transitions state.isTransitioning = true; await waitForBatchLoadingToFinish(); + setBatchLoading(true); state.isTransitioning = false; const firstSlide = parseInt( state.swiper.slides[0].dataset.globalIndex, @@ -240,6 +250,7 @@ function addGridEventListeners() { if (firstSlide > 0 && state.swiper.activeIndex === 0) { await loadBatch(index - 1, false); // Prepend a batch at the start } + setBatchLoading(false); }); // transitionEnd event @@ -333,8 +344,6 @@ async function resetAllSlides() { await new Promise(requestAnimationFrame); // display spinner - await waitForBatchLoadingToFinish(); - setBatchLoading(true); const targetIndex = slideState.getCurrentIndex(); @@ -348,11 +357,16 @@ async function resetAllSlides() { true; console.warn("removeAllSlides failed:", err); } - - await loadBatch(targetIndex, true); - await loadBatch(targetIndex + slidesPerBatch, true); // Load two batches to start in order to enable forward navigation - if (targetIndex > 0) { - await loadBatch(targetIndex, false); // Prepend a screen if not at start + try { + await waitForBatchLoadingToFinish(); + setBatchLoading(true); + await loadBatch(targetIndex, true); + await loadBatch(targetIndex + slidesPerBatch, true); // Load two batches to start in order to enable forward navigation + if (targetIndex > 0) { + await loadBatch(targetIndex, false); // Prepend a screen if not at start + } + } catch (err) { + console.log(err); } updateCurrentSlide(); setBatchLoading(false); @@ -364,8 +378,9 @@ async function resetAllSlides() { // The startIndex will be adjusted to be an even multiple of the screen size. // If startIndex is null, load the next batch after the last loaded slide. async function loadBatch(startIndex = null, append = true) { + + // Calculate the next batch to load if (startIndex === null) { - // Load after the last loaded slide if (!state.swiper.slides?.length) { startIndex = 0; } else { @@ -390,11 +405,19 @@ async function loadBatch(startIndex = null, append = true) { // --- NORMAL BATCH LOAD --- if (append) { for (let i = 0; i < slidesPerBatch; i++) { + if (i % 4 === 0) { + await new Promise(requestAnimationFrame); // yield to UI thread every 4 images + if (state.isTransitioning) { + throw new Error("Aborted loadBatch due to transition"); + } + } + // Calculate offset from current slide position const offset = startIndex + i; // Use slideState.resolveOffset to get the correct indices for this position const globalIndex = slideState.indexToGlobal(offset); + if (globalIndex === null) continue; // Out of bounds // In the event that the slide is already loaded, skip it. if (loadedImageIndices.has(globalIndex)) { @@ -435,6 +458,13 @@ async function loadBatch(startIndex = null, append = true) { } else { // --- PREPEND LOGIC: Add a full screen's worth of slides before startIndex --- for (let i = 0; i < slidesPerBatch; i++) { + if (i % 4 === 0) { + await new Promise(requestAnimationFrame); // yield to UI thread every 4 images + if (state.isTransitioning) { + throw new Error("Aborted loadBatch due to transition"); + } + } + const globalIndex = slideState.indexToGlobal(startIndex - i - 1); // reverse order if (loadedImageIndices.has(globalIndex)) continue; @@ -687,8 +717,13 @@ function setupGridResizeHandler() { const currentGlobalIndex = slideState.getCurrentSlide().globalIndex; // Reinitialize the grid completely await initializeGridSwiper(); + + // Do not allow concurrent execution! + await waitForBatchLoadingToFinish(); + setBatchLoading(true); await loadBatch(currentGlobalIndex); await loadBatch(currentGlobalIndex + slidesPerBatch); // Load two batches to start + setBatchLoading(false); } }, 300); // 300ms debounce delay } diff --git a/photomap/frontend/static/javascript/swiper.js b/photomap/frontend/static/javascript/swiper.js index bb0cc3fa..b5f0d39d 100644 --- a/photomap/frontend/static/javascript/swiper.js +++ b/photomap/frontend/static/javascript/swiper.js @@ -23,6 +23,8 @@ let isAppending = false; let isInternalSlideChange = false; // To prevent recursion in slideChange handler export async function initializeSingleSwiper() { + console.log("Initializing single swiper..."); + // The swiper shares part of the DOM with the grid view, // so we need to clean up any existing state. eventRegistry.removeAll("swiper"); // Clear previous event handlers From eeb3535dfefb6c4539e6d9e062e732f1b72b255c Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 22:25:13 -0400 Subject: [PATCH 5/8] add listener to remove umap popup thumbnails when mouse leaves window --- photomap/frontend/static/javascript/umap.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/photomap/frontend/static/javascript/umap.js b/photomap/frontend/static/javascript/umap.js index a355b8a5..3f054118 100644 --- a/photomap/frontend/static/javascript/umap.js +++ b/photomap/frontend/static/javascript/umap.js @@ -369,6 +369,12 @@ export async function fetchUmapData() { mapExists = true; } +// Add this after your Plotly event handlers in fetchUmapData() +const plotDiv = document.getElementById("umapPlot"); +plotDiv.addEventListener("mouseleave", () => { + removeUmapThumbnail(); +}); + // --- Dynamic Colorization --- export function colorizeUmap({ highlight = false, searchResults = [] } = {}) { if (!points.length) return; From 19fd311a48a7c3915dda4b1e7ef73f86c54e7d0f Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 22:27:34 -0400 Subject: [PATCH 6/8] remove dmg creation from deploy list --- .github/workflows/deploy-dmg.yml | 28 ---------------------------- .github/workflows/deploy.yml | 8 ++------ 2 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/deploy-dmg.yml diff --git a/.github/workflows/deploy-dmg.yml b/.github/workflows/deploy-dmg.yml deleted file mode 100644 index 3e79d7b7..00000000 --- a/.github/workflows/deploy-dmg.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Build macOS .dmg -on: - workflow_call: - workflow_dispatch: -jobs: - build-macos-dmg: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Install py2app - run: pip install py2app - - name: Build .app bundle - run: | - cd macos - python setup.py py2app - - name: Create .dmg - run: | - hdiutil create -volname "PhotoMapAI" -srcfolder macos/dist/PhotoMapAI.app -ov -format UDZO PhotoMapAI.dmg - - name: Upload .dmg - uses: actions/upload-artifact@v4 - with: - name: PhotoMapAI.dmg - path: PhotoMapAI.dmg - \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 21988178..fadeacaa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -50,12 +50,8 @@ jobs: needs: deploy-dockerhub uses: ./.github/workflows/deploy-pyinstaller.yml - deploy-make-dmg: - needs: deploy-pyinstaller - uses: ./.github/workflows/deploy-dmg.yml - upload-release: - needs: [deploy-pypi, deploy-dockerhub, deploy-pyinstaller, deploy-make-dmg] + needs: [deploy-pypi, deploy-dockerhub, deploy-pyinstaller] runs-on: ubuntu-latest steps: - name: Download all artifacts @@ -67,6 +63,6 @@ jobs: uses: softprops/action-gh-release@v2 with: files: | - artifacts/**/*.{zip,tar.gz,dmg} + artifacts/**/*.{zip,tar.gz,app} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From a69c242b81dbed85d35993be7667a13a662cf15c Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 22:32:55 -0400 Subject: [PATCH 7/8] remove pull action from deploy scripts - only run under make deploy.yml --- .github/workflows/deploy-pyinstaller.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy-pyinstaller.yml b/.github/workflows/deploy-pyinstaller.yml index 59683352..c46d638c 100644 --- a/.github/workflows/deploy-pyinstaller.yml +++ b/.github/workflows/deploy-pyinstaller.yml @@ -3,7 +3,6 @@ name: Deploy PyInstaller Executables on: workflow_call: workflow_dispatch: - pull_request: jobs: build: From 5e2d1520f134236809783c37de7a253bfa8a8382 Mon Sep 17 00:00:00 2001 From: Lincoln Stein Date: Wed, 1 Oct 2025 22:48:18 -0400 Subject: [PATCH 8/8] added third party license information for CLIP --- .../pyinstaller/make_pyinstaller_image.ps1 | 1 + INSTALL/pyinstaller/make_pyinstaller_image.sh | 1 + THIRD_PARTY_LICENSES.txt | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 THIRD_PARTY_LICENSES.txt diff --git a/INSTALL/pyinstaller/make_pyinstaller_image.ps1 b/INSTALL/pyinstaller/make_pyinstaller_image.ps1 index 2259dbba..402b0f3f 100644 --- a/INSTALL/pyinstaller/make_pyinstaller_image.ps1 +++ b/INSTALL/pyinstaller/make_pyinstaller_image.ps1 @@ -93,6 +93,7 @@ pyinstaller ` --add-data "$env:USERPROFILE/.cache/clip${sep}clip_models" ` --add-data "photomap/frontend/static${sep}photomap/frontend/static" ` --add-data "photomap/frontend/templates${sep}photomap/frontend/templates" ` + --add-data "THIRD_PARTY_LICENSES.txt${sep}.THIRD_PARTY_LICENSES" ` --paths . ` $pyinstallerMode ` --name photomap ` diff --git a/INSTALL/pyinstaller/make_pyinstaller_image.sh b/INSTALL/pyinstaller/make_pyinstaller_image.sh index 7b14716e..8181496d 100755 --- a/INSTALL/pyinstaller/make_pyinstaller_image.sh +++ b/INSTALL/pyinstaller/make_pyinstaller_image.sh @@ -101,6 +101,7 @@ PYINSTALLER_ARGS=( --add-data "$HOME/.cache/clip:clip_models" --add-data "photomap/frontend/static:photomap/frontend/static" --add-data "photomap/frontend/templates:photomap/frontend/templates" + --add-data "THIRD_PARTY_LICENSES.txt:THIRD_PARTY_LICENSES" --paths . $PYINSTALLER_MODE --argv-emulation diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt new file mode 100644 index 00000000..495168f5 --- /dev/null +++ b/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,20 @@ +CLIP Model Weights +Copyright (c) 2021 OpenAI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.