auto save result image when --output-dir exists

This commit is contained in:
Qing 2023-02-19 14:31:00 +08:00
parent 88a37ea904
commit 774f470e58
8 changed files with 103 additions and 38 deletions

View File

@ -8,12 +8,14 @@ import {
enableFileManagerState, enableFileManagerState,
fileState, fileState,
isDisableModelSwitchState, isDisableModelSwitchState,
isEnableAutoSavingState,
toastState, toastState,
} from './store/Atoms' } from './store/Atoms'
import { keepGUIAlive } from './utils' import { keepGUIAlive } from './utils'
import Header from './components/Header/Header' import Header from './components/Header/Header'
import useHotKey from './hooks/useHotkey' import useHotKey from './hooks/useHotkey'
import { import {
getEnableAutoSaving,
getEnableFileManager, getEnableFileManager,
getIsDisableModelSwitch, getIsDisableModelSwitch,
isDesktop, isDesktop,
@ -34,6 +36,7 @@ function App() {
const userInputImage = useInputImage() const userInputImage = useInputImage()
const setIsDisableModelSwitch = useSetRecoilState(isDisableModelSwitchState) const setIsDisableModelSwitch = useSetRecoilState(isDisableModelSwitchState)
const setEnableFileManager = useSetRecoilState(enableFileManagerState) const setEnableFileManager = useSetRecoilState(enableFileManagerState)
const setIsEnableAutoSavingState = useSetRecoilState(isEnableAutoSavingState)
// Set Input Image // Set Input Image
useEffect(() => { useEffect(() => {
@ -66,7 +69,17 @@ function App() {
setEnableFileManager(isEnabled === 'true') setEnableFileManager(isEnabled === 'true')
} }
fetchData2() fetchData2()
}, [setEnableFileManager, setIsDisableModelSwitch])
const fetchData3 = async () => {
const isEnabled = await getEnableAutoSaving().then(res => res.text())
setIsEnableAutoSavingState(isEnabled === 'true')
}
fetchData3()
}, [
setEnableFileManager,
setIsDisableModelSwitch,
setIsEnableAutoSavingState,
])
// Dark Mode Hotkey // Dark Mode Hotkey
useHotKey( useHotKey(

View File

@ -122,6 +122,12 @@ export function getEnableFileManager() {
}) })
} }
export function getEnableAutoSaving() {
return fetch(`${API_ENDPOINT}/is_enable_auto_saving`, {
method: 'GET',
})
}
export function switchModel(name: string) { export function switchModel(name: string) {
const fd = new FormData() const fd = new FormData()
fd.append('name', name) fd.append('name', name)

View File

@ -20,7 +20,6 @@ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use' import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
import inpaint, { import inpaint, {
downloadToOutput, downloadToOutput,
makeGif,
postInteractiveSeg, postInteractiveSeg,
} from '../../adapters/inpainting' } from '../../adapters/inpainting'
import Button from '../shared/Button' import Button from '../shared/Button'
@ -45,12 +44,11 @@ import {
imageWidthState, imageWidthState,
interactiveSegClicksState, interactiveSegClicksState,
isDiffusionModelsState, isDiffusionModelsState,
isEnableAutoSavingState,
isInpaintingState, isInpaintingState,
isInteractiveSegRunningState, isInteractiveSegRunningState,
isInteractiveSegState, isInteractiveSegState,
isPaintByExampleState,
isPix2PixState, isPix2PixState,
isSDState,
negativePropmtState, negativePropmtState,
propmtState, propmtState,
runManuallyState, runManuallyState,
@ -184,6 +182,7 @@ export default function Editor() {
const [redoCurLines, setRedoCurLines] = useState<Line[]>([]) const [redoCurLines, setRedoCurLines] = useState<Line[]>([])
const [redoLineGroups, setRedoLineGroups] = useState<LineGroup[]>([]) const [redoLineGroups, setRedoLineGroups] = useState<LineGroup[]>([])
const enableFileManager = useRecoilValue(enableFileManagerState) const enableFileManager = useRecoilValue(enableFileManagerState)
const isEnableAutoSaving = useRecoilValue(isEnableAutoSavingState)
const setImageWidth = useSetRecoilState(imageWidthState) const setImageWidth = useSetRecoilState(imageWidthState)
const setImageHeight = useSetRecoilState(imageHeightState) const setImageHeight = useSetRecoilState(imageHeightState)
@ -1101,7 +1100,7 @@ export default function Editor() {
if (file === undefined) { if (file === undefined) {
return return
} }
if (enableFileManager && renders.length > 0) { if ((enableFileManager || isEnableAutoSaving) && renders.length > 0) {
try { try {
downloadToOutput(renders[renders.length - 1], file.name, file.type) downloadToOutput(renders[renders.length - 1], file.name, file.type)
setToastState({ setToastState({

View File

@ -41,6 +41,7 @@ interface AppState {
disableShortCuts: boolean disableShortCuts: boolean
isInpainting: boolean isInpainting: boolean
isDisableModelSwitch: boolean isDisableModelSwitch: boolean
isEnableAutoSaving: boolean
isInteractiveSeg: boolean isInteractiveSeg: boolean
isInteractiveSegRunning: boolean isInteractiveSegRunning: boolean
interactiveSegClicks: number[][] interactiveSegClicks: number[][]
@ -58,6 +59,7 @@ export const appState = atom<AppState>({
disableShortCuts: false, disableShortCuts: false,
isInpainting: false, isInpainting: false,
isDisableModelSwitch: false, isDisableModelSwitch: false,
isEnableAutoSaving: false,
isInteractiveSeg: false, isInteractiveSeg: false,
isInteractiveSegRunning: false, isInteractiveSegRunning: false,
interactiveSegClicks: [], interactiveSegClicks: [],
@ -221,6 +223,18 @@ export const isDisableModelSwitchState = selector({
}, },
}) })
export const isEnableAutoSavingState = selector({
key: 'isEnableAutoSavingState',
get: ({ get }) => {
const app = get(appState)
return app.isEnableAutoSaving
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, isEnableAutoSaving: newValue })
},
})
export const croperState = atom<Rect>({ export const croperState = atom<Rect>({
key: 'croperState', key: 'croperState',
default: { default: {

View File

@ -59,7 +59,7 @@ Model download directory (by setting XDG_CACHE_HOME environment variable), by de
""" """
OUTPUT_DIR_HELP = """ OUTPUT_DIR_HELP = """
Only required when --input is directory. Result images will be saved to output directory automatically. Result images will be saved to output directory automatically without confirmation.
""" """
INPUT_HELP = """ INPUT_HELP = """

View File

@ -14,7 +14,7 @@ from PIL import Image, ImageOps, PngImagePlugin
from loguru import logger from loguru import logger
LARGE_ENOUGH_NUMBER = 100 LARGE_ENOUGH_NUMBER = 100
PngImagePlugin.MAX_TEXT_CHUNK = LARGE_ENOUGH_NUMBER * (1024 ** 2) PngImagePlugin.MAX_TEXT_CHUNK = LARGE_ENOUGH_NUMBER * (1024**2)
from .storage_backends import FilesystemStorageBackend from .storage_backends import FilesystemStorageBackend
from .utils import aspect_to_string, generate_filename, glob_img from .utils import aspect_to_string, generate_filename, glob_img
@ -63,11 +63,11 @@ class FileManager(FileSystemEventHandler):
if event.src_path == str(self.root_directory): if event.src_path == str(self.root_directory):
logger.info(f"Image directory {event.src_path} modified") logger.info(f"Image directory {event.src_path} modified")
self.image_dir_filenames = self._media_names(self.root_directory) self.image_dir_filenames = self._media_names(self.root_directory)
self.modified_time['image'] = datetime.utcnow() self.modified_time["image"] = datetime.utcnow()
elif event.src_path == str(self.output_dir): elif event.src_path == str(self.output_dir):
logger.info(f"Output directory {event.src_path} modified") logger.info(f"Output directory {event.src_path} modified")
self.output_dir_filenames = self._media_names(self.output_dir) self.output_dir_filenames = self._media_names(self.output_dir)
self.modified_time['output'] = datetime.utcnow() self.modified_time["output"] = datetime.utcnow()
def init_app(self, app): def init_app(self, app):
if self.app is None: if self.app is None:
@ -83,21 +83,15 @@ class FileManager(FileSystemEventHandler):
app.extensions["thumbnail"] = self app.extensions["thumbnail"] = self
app.config.setdefault("THUMBNAIL_MEDIA_ROOT", self._default_root_directory) app.config.setdefault("THUMBNAIL_MEDIA_ROOT", self._default_root_directory)
app.config.setdefault("THUMBNAIL_MEDIA_THUMBNAIL_ROOT", self._default_thumbnail_directory) app.config.setdefault(
"THUMBNAIL_MEDIA_THUMBNAIL_ROOT", self._default_thumbnail_directory
)
app.config.setdefault("THUMBNAIL_MEDIA_URL", self._default_root_url) app.config.setdefault("THUMBNAIL_MEDIA_URL", self._default_root_url)
app.config.setdefault("THUMBNAIL_MEDIA_THUMBNAIL_URL", self._default_thumbnail_root_url) app.config.setdefault(
"THUMBNAIL_MEDIA_THUMBNAIL_URL", self._default_thumbnail_root_url
)
app.config.setdefault("THUMBNAIL_DEFAULT_FORMAT", self._default_format) app.config.setdefault("THUMBNAIL_DEFAULT_FORMAT", self._default_format)
def save_to_output_directory(self, image: np.ndarray, filename: str):
fp = Path(filename)
new_name = fp.stem + f"_{int(time.time())}" + fp.suffix
if image.shape[2] == 3:
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
elif image.shape[2] == 4:
image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
cv2.imwrite(str(self.output_dir / new_name), image)
@property @property
def root_directory(self): def root_directory(self):
path = self.app.config["THUMBNAIL_MEDIA_ROOT"] path = self.app.config["THUMBNAIL_MEDIA_ROOT"]
@ -137,14 +131,23 @@ class FileManager(FileSystemEventHandler):
for name in names: for name in names:
path = os.path.join(directory, name) path = os.path.join(directory, name)
img = Image.open(path) img = Image.open(path)
res.append({"name": name, "height": img.height, "width": img.width, "ctime": os.path.getctime(path)}) res.append(
{
"name": name,
"height": img.height,
"width": img.width,
"ctime": os.path.getctime(path),
}
)
return res return res
@property @property
def thumbnail_url(self): def thumbnail_url(self):
return self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_URL"] return self.app.config["THUMBNAIL_MEDIA_THUMBNAIL_URL"]
def get_thumbnail(self, directory: Path, original_filename: str, width, height, **options): def get_thumbnail(
self, directory: Path, original_filename: str, width, height, **options
):
storage = FilesystemStorageBackend(self.app) storage = FilesystemStorageBackend(self.app)
crop = options.get("crop", "fit") crop = options.get("crop", "fit")
background = options.get("background") background = options.get("background")
@ -163,13 +166,19 @@ class FileManager(FileSystemEventHandler):
thumbnail_size = (width, height) thumbnail_size = (width, height)
thumbnail_filename = generate_filename( thumbnail_filename = generate_filename(
original_filename, aspect_to_string(thumbnail_size), crop, background, quality original_filename,
aspect_to_string(thumbnail_size),
crop,
background,
quality,
) )
thumbnail_filepath = os.path.join( thumbnail_filepath = os.path.join(
self.thumbnail_directory, original_path, thumbnail_filename self.thumbnail_directory, original_path, thumbnail_filename
) )
thumbnail_url = os.path.join(self.thumbnail_url, original_path, thumbnail_filename) thumbnail_url = os.path.join(
self.thumbnail_url, original_path, thumbnail_filename
)
if storage.exists(thumbnail_filepath): if storage.exists(thumbnail_filepath):
return thumbnail_url, (width, height) return thumbnail_url, (width, height)
@ -183,7 +192,9 @@ class FileManager(FileSystemEventHandler):
# get original image format # get original image format
options["format"] = options.get("format", image.format) options["format"] = options.get("format", image.format)
image = self._create_thumbnail(image, thumbnail_size, crop, background=background) image = self._create_thumbnail(
image, thumbnail_size, crop, background=background
)
raw_data = self.get_raw_data(image, **options) raw_data = self.get_raw_data(image, **options)
storage.save(thumbnail_filepath, raw_data) storage.save(thumbnail_filepath, raw_data)

View File

@ -170,15 +170,14 @@ def parse_args():
parser.error( parser.error(
f"invalid --input: {args.input} is a directory, --output-dir is required" f"invalid --input: {args.input} is a directory, --output-dir is required"
) )
else:
if args.output_dir is not None:
output_dir = Path(args.output_dir) output_dir = Path(args.output_dir)
if not output_dir.exists(): if not output_dir.exists():
logger.info(f"Creating output directory: {output_dir}") logger.info(f"Creating output directory: {output_dir}")
output_dir.mkdir(parents=True) output_dir.mkdir(parents=True)
else: else:
if not output_dir.is_dir(): if not output_dir.is_dir():
parser.error( parser.error(f"invalid --output-dir: {output_dir} is not a directory")
f"invalid --output-dir: {output_dir} is not a directory"
)
return args return args

View File

@ -83,11 +83,13 @@ CORS(app, expose_headers=["Content-Disposition"])
model: ModelManager = None model: ModelManager = None
thumb: FileManager = None thumb: FileManager = None
output_dir: str = None
interactive_seg_model: InteractiveSeg = None interactive_seg_model: InteractiveSeg = None
device = None device = None
input_image_path: str = None input_image_path: str = None
is_disable_model_switch: bool = False is_disable_model_switch: bool = False
is_enable_file_manager: bool = False is_enable_file_manager: bool = False
is_enable_auto_saving: bool = False
is_desktop: bool = False is_desktop: bool = False
@ -124,11 +126,20 @@ def make_gif():
@app.route("/save_image", methods=["POST"]) @app.route("/save_image", methods=["POST"])
def save_image(): def save_image():
# all image in output directory if output_dir is None:
return "--output-dir is None", 500
input = request.files input = request.files
filename = request.form["filename"]
origin_image_bytes = input["image"].read() # RGB origin_image_bytes = input["image"].read() # RGB
image, _ = load_img(origin_image_bytes) image, _ = load_img(origin_image_bytes)
thumb.save_to_output_directory(image, request.form["filename"]) if image.shape[2] == 3:
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
elif image.shape[2] == 4:
image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGRA)
cv2.imwrite(os.path.join(output_dir, filename), image)
return "ok", 200 return "ok", 200
@ -348,6 +359,12 @@ def get_is_enable_file_manager():
return res, 200 return res, 200
@app.route("/is_enable_auto_saving")
def get_is_enable_auto_saving():
res = "true" if is_enable_auto_saving else "false"
return res, 200
@app.route("/model_downloaded/<name>") @app.route("/model_downloaded/<name>")
def model_downloaded(name): def model_downloaded(name):
return str(model.is_downloaded(name)), 200 return str(model.is_downloaded(name)), 200
@ -408,6 +425,12 @@ def main(args):
global is_enable_file_manager global is_enable_file_manager
global is_desktop global is_desktop
global thumb global thumb
global output_dir
global is_enable_auto_saving
output_dir = args.output_dir
if output_dir is not None:
is_enable_auto_saving = True
device = torch.device(args.device) device = torch.device(args.device)
is_disable_model_switch = args.disable_model_switch is_disable_model_switch = args.disable_model_switch