commit
1a8d407782
@ -6,6 +6,7 @@ https://user-images.githubusercontent.com/3998421/153323093-b664bb68-2928-480b-b
|
|||||||
1. [LaMa](https://github.com/saic-mdal/lama)
|
1. [LaMa](https://github.com/saic-mdal/lama)
|
||||||
1. [LDM](https://github.com/CompVis/latent-diffusion)
|
1. [LDM](https://github.com/CompVis/latent-diffusion)
|
||||||
- [x] High resolution support
|
- [x] High resolution support
|
||||||
|
- [x] Run as a desktop APP
|
||||||
- [x] Multi stroke support. Press and hold the `cmd/ctrl` key to enable multi stroke mode.
|
- [x] Multi stroke support. Press and hold the `cmd/ctrl` key to enable multi stroke mode.
|
||||||
- [x] Zoom & Pan
|
- [x] Zoom & Pan
|
||||||
- [ ] Keep image EXIF data
|
- [ ] Keep image EXIF data
|
||||||
@ -24,9 +25,11 @@ Available commands for `main.py`
|
|||||||
| --ldm-steps | The larger the value, the better the result, but it will be more time-consuming | 50 |
|
| --ldm-steps | The larger the value, the better the result, but it will be more time-consuming | 50 |
|
||||||
| --crop-trigger-size | If image size large then crop-trigger-size, crop each area from original image to do inference. Mainly for performance and memory reasons on **very** large image. | 2042,2042 |
|
| --crop-trigger-size | If image size large then crop-trigger-size, crop each area from original image to do inference. Mainly for performance and memory reasons on **very** large image. | 2042,2042 |
|
||||||
| --crop-margin | Margin around bounding box of painted stroke when crop mode triggered. | 256 |
|
| --crop-margin | Margin around bounding box of painted stroke when crop mode triggered. | 256 |
|
||||||
| --port | Port for web server | 8080 |
|
|
||||||
| --gui | Launch lama-cleaner as a desktop application | |
|
| --gui | Launch lama-cleaner as a desktop application | |
|
||||||
| --gui_size | Set the window size for the application | 1200 900 |
|
| --gui_size | Set the window size for the application | 1200 900 |
|
||||||
|
| --input | Path to image you want to load by default | None |
|
||||||
|
| --port | Port for flask web server | 8080 |
|
||||||
|
| --debug | Enable debug mode for flask web server | |
|
||||||
|
|
||||||
## Model Comparison
|
## Model Comparison
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.0a04cd80.chunk.css",
|
"main.css": "/static/css/main.1144a0ea.chunk.css",
|
||||||
"main.js": "/static/js/main.288df200.chunk.js",
|
"main.js": "/static/js/main.98890b3e.chunk.js",
|
||||||
"runtime-main.js": "/static/js/runtime-main.5e86ac81.js",
|
"runtime-main.js": "/static/js/runtime-main.5e86ac81.js",
|
||||||
"static/js/2.d3149f41.chunk.js": "/static/js/2.d3149f41.chunk.js",
|
"static/js/2.d3149f41.chunk.js": "/static/js/2.d3149f41.chunk.js",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/js/runtime-main.5e86ac81.js",
|
"static/js/runtime-main.5e86ac81.js",
|
||||||
"static/js/2.d3149f41.chunk.js",
|
"static/js/2.d3149f41.chunk.js",
|
||||||
"static/css/main.0a04cd80.chunk.css",
|
"static/css/main.1144a0ea.chunk.css",
|
||||||
"static/js/main.288df200.chunk.js"
|
"static/js/main.98890b3e.chunk.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#ffffff"/><title>lama-cleaner - Image inpainting powered by LaMa</title><link href="/static/css/main.0a04cd80.chunk.css" rel="stylesheet"></head><body class="h-screen"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="h-full"></div><script>"localhost"===location.hostname&&(self.FIREBASE_APPCHECK_DEBUG_TOKEN=!0)</script><script>!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],p=0,s=[];p<a.length;p++)l=a[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var f=t[a];0!==o[f]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonplama-cleaner"]=this["webpackJsonplama-cleaner"]||[],f=a.push.bind(a);a.push=r,a=a.slice();for(var i=0;i<a.length;i++)r(a[i]);var c=f;t()}([])</script><script src="/static/js/2.d3149f41.chunk.js"></script><script src="/static/js/main.288df200.chunk.js"></script></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#ffffff"/><title>lama-cleaner - Image inpainting powered by LaMa</title><link href="/static/css/main.1144a0ea.chunk.css" rel="stylesheet"></head><body class="h-screen"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="h-full"></div><script>"localhost"===location.hostname&&(self.FIREBASE_APPCHECK_DEBUG_TOKEN=!0)</script><script>!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],p=0,s=[];p<a.length;p++)l=a[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var f=t[a];0!==o[f]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonplama-cleaner"]=this["webpackJsonplama-cleaner"]||[],f=a.push.bind(a);a.push=r,a=a.slice();for(var i=0;i<a.length;i++)r(a[i]);var c=f;t()}([])</script><script src="/static/js/2.d3149f41.chunk.js"></script><script src="/static/js/main.98890b3e.chunk.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
@ -1,8 +1,9 @@
|
|||||||
import { ArrowLeftIcon } from '@heroicons/react/outline'
|
import { ArrowLeftIcon } from '@heroicons/react/outline'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useToggle, useWindowSize } from 'react-use'
|
import { useToggle, useWindowSize } from 'react-use'
|
||||||
import Button from './components/Button'
|
import Button from './components/Button'
|
||||||
import FileSelect from './components/FileSelect'
|
import FileSelect from './components/FileSelect'
|
||||||
|
import useInputImage from './hooks/useInputImage'
|
||||||
import ShortcutsModal from './components/ShortcutsModal'
|
import ShortcutsModal from './components/ShortcutsModal'
|
||||||
import Editor from './Editor'
|
import Editor from './Editor'
|
||||||
|
|
||||||
@ -31,6 +32,11 @@ function App() {
|
|||||||
const [file, setFile] = useState<File>()
|
const [file, setFile] = useState<File>()
|
||||||
const [showShortcuts, toggleShowShortcuts] = useToggle(false)
|
const [showShortcuts, toggleShowShortcuts] = useToggle(false)
|
||||||
const windowSize = useWindowSize()
|
const windowSize = useWindowSize()
|
||||||
|
const userInputImage = useInputImage()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFile(userInputImage)
|
||||||
|
}, [userInputImage])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full full-visible-h-safari flex flex-col">
|
<div className="h-full full-visible-h-safari flex flex-col">
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { ArrowLeftIcon } from '@heroicons/react/outline'
|
|
||||||
import React, { ReactNode } from 'react'
|
import React, { ReactNode } from 'react'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { FocusEvent, useCallback, useEffect } from 'react'
|
import React, { FocusEvent, useCallback } from 'react'
|
||||||
import { Listbox } from '@headlessui/react'
|
import { Listbox } from '@headlessui/react'
|
||||||
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
|
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
|
26
lama_cleaner/app/src/hooks/useInputImage.tsx
Normal file
26
lama_cleaner/app/src/hooks/useInputImage.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export default function useInputImage() {
|
||||||
|
const [inputImage, setInputImage] = useState<File>()
|
||||||
|
|
||||||
|
const fetchInputImage = useCallback(() => {
|
||||||
|
const headers = new Headers()
|
||||||
|
headers.append('pragma', 'no-cache')
|
||||||
|
headers.append('cache-control', 'no-cache')
|
||||||
|
|
||||||
|
fetch('/inputimage', { headers })
|
||||||
|
.then(res => res.blob())
|
||||||
|
.then(data => {
|
||||||
|
if (data && data.type.startsWith('image')) {
|
||||||
|
const userInput = new File([data], 'inputImage')
|
||||||
|
setInputImage(userInput)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [setInputImage])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInputImage()
|
||||||
|
}, [fetchInputImage])
|
||||||
|
|
||||||
|
return inputImage
|
||||||
|
}
|
32
main.py
32
main.py
@ -5,6 +5,7 @@ import io
|
|||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import imghdr
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@ -52,6 +53,7 @@ CORS(app)
|
|||||||
|
|
||||||
model = None
|
model = None
|
||||||
device = None
|
device = None
|
||||||
|
input_image_path: str = None
|
||||||
|
|
||||||
|
|
||||||
@app.route("/inpaint", methods=["POST"])
|
@app.route("/inpaint", methods=["POST"])
|
||||||
@ -98,11 +100,23 @@ def index():
|
|||||||
return send_file(os.path.join(BUILD_DIR, "index.html"))
|
return send_file(os.path.join(BUILD_DIR, "index.html"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/inputimage')
|
||||||
|
def set_input_photo():
|
||||||
|
if input_image_path:
|
||||||
|
with open(input_image_path, 'rb') as f:
|
||||||
|
image_in_bytes = f.read()
|
||||||
|
return send_file(io.BytesIO(image_in_bytes), mimetype='image/jpeg')
|
||||||
|
else:
|
||||||
|
return 'No Input Image'
|
||||||
|
|
||||||
|
|
||||||
def get_args_parser():
|
def get_args_parser():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--input", type=str, help="Path to image you want to load by default")
|
||||||
parser.add_argument("--port", default=8080, type=int)
|
parser.add_argument("--port", default=8080, type=int)
|
||||||
parser.add_argument("--model", default="lama", choices=["lama", "ldm"])
|
parser.add_argument("--model", default="lama", choices=["lama", "ldm"])
|
||||||
parser.add_argument("--crop-trigger-size", nargs=2, type=int,
|
parser.add_argument("--crop-trigger-size", default=[2042, 2042], nargs=2, type=int,
|
||||||
help="If image size large then crop-trigger-size, "
|
help="If image size large then crop-trigger-size, "
|
||||||
"crop each area from original image to do inference."
|
"crop each area from original image to do inference."
|
||||||
"Mainly for performance and memory reasons"
|
"Mainly for performance and memory reasons"
|
||||||
@ -122,17 +136,29 @@ def get_args_parser():
|
|||||||
parser.add_argument("--gui-size", default=[1600, 1000], nargs=2, type=int,
|
parser.add_argument("--gui-size", default=[1600, 1000], nargs=2, type=int,
|
||||||
help="Set window size for GUI")
|
help="Set window size for GUI")
|
||||||
parser.add_argument("--debug", action="store_true")
|
parser.add_argument("--debug", action="store_true")
|
||||||
return parser.parse_args()
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.input is not None:
|
||||||
|
if not os.path.exists(args.input):
|
||||||
|
parser.error(f"invalid --input: {args.input} not exists")
|
||||||
|
if imghdr.what(args.input) is None:
|
||||||
|
parser.error(f"invalid --input: {args.input} is not a valid image file")
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global model
|
global model
|
||||||
global device
|
global device
|
||||||
|
global input_image_path
|
||||||
|
|
||||||
args = get_args_parser()
|
args = get_args_parser()
|
||||||
device = torch.device(args.device)
|
device = torch.device(args.device)
|
||||||
|
input_image_path = args.input
|
||||||
|
|
||||||
if args.model == "lama":
|
if args.model == "lama":
|
||||||
model = LaMa(crop_trigger_size=args.crop_trigger_size, crop_margin=args.crop_margin, device=device)
|
model = LaMa(crop_trigger_size=args.crop_trigger_size,
|
||||||
|
crop_margin=args.crop_margin, device=device)
|
||||||
elif args.model == "ldm":
|
elif args.model == "ldm":
|
||||||
model = LDM(device, steps=args.ldm_steps)
|
model = LDM(device, steps=args.ldm_steps)
|
||||||
else:
|
else:
|
||||||
|
@ -4,3 +4,4 @@ flask_cors
|
|||||||
flask
|
flask
|
||||||
flaskwebgui
|
flaskwebgui
|
||||||
tqdm
|
tqdm
|
||||||
|
imghdr
|
||||||
|
Loading…
Reference in New Issue
Block a user