Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/features/NSFW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: The NSFW Checker
---

# :material-image-off: NSFW Checker

## The NSFW ("Safety") Checker

The Stable Diffusion image generation models will produce sexual
imagery if deliberately prompted, and will occasionally produce such
images when this is not intended. Such images are colloquially known
as "Not Safe for Work" (NSFW). This behavior is due to the nature of
the training set that Stable Diffusion was trained on, which culled
millions of "aesthetic" images from the Internet.

You may not wish to be exposed to these images, and in some
jurisdictions it may be illegal to publicly distribute such imagery,
including mounting a publicly-available server that provides
unfiltered images to the public. Furthermore, the [Stable Diffusion
weights
License](https://github.com/invoke-ai/InvokeAI/blob/main/LICENSE-ModelWeights.txt)
forbids the model from being used to "exploit any of the
vulnerabilities of a specific group of persons."

For these reasons Stable Diffusion offers a "safety checker," a
machine learning model trained to recognize potentially disturbing
imagery. When a potentially NSFW image is detected, the checker will
blur the image and paste a warning icon on top. The checker can be
turned on and off on the command line using `--nsfw_checker` and
`--no-nsfw_checker`.

At installation time, InvokeAI will ask whether the checker should be
activated by default (neither argument given on the command line). The
response is stored in the InvokeAI initialization file (usually
`.invokeai` in your home directory). You can change the default at any
time by opening this file in a text editor and commenting or
uncommenting the line `--nsfw_checker`.

## Caveats

There are a number of caveats that you need to be aware of.

### Accuracy

The checker is [not perfect](https://arxiv.org/abs/2210.04610).It will
occasionally flag innocuous images (false positives), and will
frequently miss violent and gory imagery (false negatives). It rarely
fails to flag sexual imagery, but this has been known to happen. For
these reasons, the InvokeAI team prefers to refer to the software as a
"NSFW Checker" rather than "safety checker."

### Memory Usage and Performance

The NSFW checker consumes an additional 1.2G of GPU VRAM on top of the
3.4G of VRAM used by Stable Diffusion v1.5 (this is with
half-precision arithmetic). This means that the checker will not run
successfully on GPU cards with less than 6GB VRAM, and will reduce the
size of the images that you can produce.

The checker also introduces a slight performance penalty. Images will
take ~1 second longer to generate when the checker is
activated. Generally this is not noticeable.

### Intermediate Images in the Web UI

The checker only operates on the final image produced by the Stable
Diffusion algorithm. If you are using the Web UI and have enabled the
display of intermediate images, you will briefly be exposed to a
low-resolution (mosaicized) version of the final image before it is
flagged by the checker and replaced by a fully blurred version. You
are encouraged to turn **off** intermediate image rendering when you
are using the checker. Future versions of InvokeAI will apply
additional blurring to intermediate images when the checker is active.

### Watermarking

InvokeAI does not apply any sort of watermark to images it
generates. However, it does write metadata into the PNG data area,
including the prompt used to generate the image and relevant parameter
settings. These fields can be examined using the `sd-metadata.py`
script that comes with the InvokeAI package.

Note that several other Stable Diffusion distributions offer
wavelet-based "invisible" watermarking. We have experimented with the
library used to generate these watermarks and have reached the
conclusion that while the watermarking library may be adding
watermarks to PNG images, the currently available version is unable to
retrieve them successfully. If and when a functioning version of the
library becomes available, we will offer this feature as well.
2 changes: 1 addition & 1 deletion ldm/invoke/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ def _create_arg_parser(self):
action=argparse.BooleanOptionalAction,
dest='safety_checker',
default=False,
help='Check for and blur potentially NSFW images.',
help='Check for and blur potentially NSFW images. Use --no-nsfw_checker to disable.',
)
model_group.add_argument(
'--patchmatch',
Expand Down
24 changes: 21 additions & 3 deletions ldm/invoke/generator/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np
import random
import os
import os.path as osp
import traceback
from tqdm import tqdm, trange
from PIL import Image, ImageFilter, ImageChops
Expand All @@ -32,6 +33,7 @@ def __init__(self, model, precision):
self.with_variations = []
self.use_mps_noise = False
self.free_gpu_mem = None
self.caution_img = None

# this is going to be overridden in img2img.py, txt2img.py and inpaint.py
def get_make_image(self,prompt,**kwargs):
Expand Down Expand Up @@ -290,13 +292,29 @@ def safety_check(self,image:Image.Image):
def blur(self,input):
blurry = input.filter(filter=ImageFilter.GaussianBlur(radius=32))
try:
caution = Image.open(CAUTION_IMG)
caution = caution.resize((caution.width // 2, caution.height //2))
blurry.paste(caution,(0,0),caution)
caution = self.get_caution_img()
if caution:
blurry.paste(caution,(0,0),caution)
except FileNotFoundError:
pass
return blurry

def get_caution_img(self):
if self.caution_img:
return self.caution_img
# Find the caution image. If we are installed in the package directory it will
# be six levels up. If we are in the repo directory it will be three levels up.
for dots in ('../../..','../../../../../..'):
caution_path = osp.join(osp.dirname(__file__),dots,CAUTION_IMG)
if osp.exists(caution_path):
path = caution_path
break
if not path:
return
caution = Image.open(path)
self.caution_img = caution.resize((caution.width // 2, caution.height //2))
return self.caution_img

# this is a handy routine for debugging use. Given a generated sample,
# convert it into a PNG image and store it at the indicated path
def save_sample(self, sample, filepath):
Expand Down
77 changes: 52 additions & 25 deletions scripts/configure_invokeai.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ def postscript():
Command-line version:
python scripts/invoke.py

Remember to activate that 'invokeai' environment before running invoke.py.

Or, if you used one of the automated installers, execute "invoke.sh" (Linux/Mac)
or "invoke.bat" (Windows) to start the script.
If you installed manually, remember to activate the 'invokeai'
environment before running invoke.py. If you installed using the
automated installation script, execute "invoke.sh" (Linux/Mac) or
"invoke.bat" (Windows) to start InvokeAI.

Have fun!
'''
Expand Down Expand Up @@ -243,10 +243,10 @@ def download_weight_datasets(models:dict, access_token:str):
for mod in models.keys():
repo_id = Datasets[mod]['repo_id']
filename = Datasets[mod]['file']
print(os.path.join(Globals.root,Model_dir,Weights_dir), file=sys.stderr)
dest = os.path.join(Globals.root,Model_dir,Weights_dir)
success = hf_download_with_resume(
repo_id=repo_id,
model_dir=os.path.join(Globals.root,Model_dir,Weights_dir),
model_dir=dest,
model_name=filename,
access_token=access_token
)
Expand Down Expand Up @@ -494,12 +494,12 @@ def download_clipseg():

#-------------------------------------
def download_safety_checker():
print('Installing safety model for NSFW content detection...',file=sys.stderr)
print('Installing model for NSFW content detection...',file=sys.stderr)
try:
from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker
from transformers import AutoFeatureExtractor
except ModuleNotFoundError:
print('Error installing safety checker model:')
print('Error installing NSFW checker model:')
print(traceback.format_exc())
return
safety_model_id = "CompVis/stable-diffusion-safety-checker"
Expand All @@ -520,6 +520,7 @@ def download_weights(opt:dict):
return
else:
print('** Cannot download models because no Hugging Face access token could be found. Please re-run without --yes')
return
else:
choice = user_wants_to_download_weights()

Expand Down Expand Up @@ -584,7 +585,7 @@ def select_outputs(root:str,yes_to_all:bool=False):

#-------------------------------------
def initialize_rootdir(root:str,yes_to_all:bool=False):
assert os.path.exists('./configs'),'Run this script from within the top level of the InvokeAI source code directory, "InvokeAI"'
assert os.path.exists('./configs'),'Run this script from within the InvokeAI source code directory, "InvokeAI" or the runtime directory "invokeai".'

print(f'** INITIALIZING INVOKEAI RUNTIME DIRECTORY **')
root_selected = False
Expand All @@ -603,19 +604,50 @@ def initialize_rootdir(root:str,yes_to_all:bool=False):
print(f'\nYou may change the chosen directories at any time by editing the --root and --outdir options in "{Globals.initfile}",')
print(f'You may also change the runtime directory by setting the environment variable INVOKEAI_ROOT.\n')

enable_safety_checker = True
default_sampler = 'k_heun'
default_steps = '20' # deliberately a string - see test below

sampler_choices =['ddim','k_dpm_2_a','k_dpm_2','k_euler_a','k_euler','k_heun','k_lms','plms']

if not yes_to_all:
print('The NSFW (not safe for work) checker blurs out images that potentially contain sexual imagery.')
print('It can be selectively enabled at run time with --nsfw_checker, and disabled with --no-nsfw_checker.')
print('The following option will set whether the checker is enabled by default. Like other options, you can')
print(f'change this setting later by editing the file {Globals.initfile}.')
enable_safety_checker = yes_or_no('Enable the NSFW checker by default?',enable_safety_checker)

print('\nThe next choice selects the sampler to use by default. Samplers have different speed/performance')
print('tradeoffs. If you are not sure what to select, accept the default.')
sampler = None
while sampler not in sampler_choices:
sampler = input(f'Default sampler to use? ({", ".join(sampler_choices)}) [{default_sampler}]:') or default_sampler

print('\nThe number of denoising steps affects both the speed and quality of the images generated.')
print('Higher steps often (but not always) increases the quality of the image, but increases image')
print('generation time. This can be changed at run time. Accept the default if you are unsure.')
steps = ''
while not steps.isnumeric():
steps = input(f'Default number of steps to use during generation? [{default_steps}]:') or default_steps
else:
sampler = default_sampler
steps = default_steps

safety_checker = '--nsfw_checker' if enable_safety_checker else '--no-nsfw_checker'

for name in ('models','configs','embeddings'):
os.makedirs(os.path.join(root,name), exist_ok=True)
for src in (['configs']):
dest = os.path.join(root,src)
if not os.path.samefile(src,dest):
shutil.copytree(src,dest,dirs_exist_ok=True)
os.makedirs(outputs, exist_ok=True)
os.makedirs(outputs, exist_ok=True)

init_file = os.path.expanduser(Globals.initfile)
if not os.path.exists(init_file):
print(f'Creating the initialization file at "{init_file}".\n')
with open(init_file,'w') as f:
f.write(f'''# InvokeAI initialization file

print(f'Creating the initialization file at "{init_file}".\n')
with open(init_file,'w') as f:
f.write(f'''# InvokeAI initialization file
# This is the InvokeAI initialization file, which contains command-line default values.
# Feel free to edit. If anything goes wrong, you can re-initialize this file by deleting
# or renaming it and then running configure_invokeai.py again.
Expand All @@ -626,23 +658,18 @@ def initialize_rootdir(root:str,yes_to_all:bool=False):
# the --outdir option controls the default location of image files.
--outdir="{outputs}"

# generation arguments
{safety_checker}
--sampler={sampler}
--steps={steps}

# You may place other frequently-used startup commands here, one or more per line.
# Examples:
# --web --host=0.0.0.0
# --steps=20
# -Ak_euler_a -C10.0
#
'''
)
else:
print(f'Updating the initialization file at "{init_file}".\n')
with open(init_file,'r') as infile, open(f'{init_file}.tmp','w') as outfile:
for line in infile.readlines():
if not line.startswith('--root') and not line.startswith('--outdir'):
outfile.write(line)
outfile.write(f'--root="{root}"\n')
outfile.write(f'--outdir="{outputs}"\n')
os.replace(f'{init_file}.tmp',init_file)
''')

#-------------------------------------
class ProgressBar():
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def list_files(directory):
'scripts/preload_models.py', 'scripts/images2prompt.py','scripts/merge_embeddings.py'
],
data_files=[('frontend/dist',list_files('frontend/dist')),
('frontend/dist/assets',list_files('frontend/dist/assets'))
('frontend/dist/assets',list_files('frontend/dist/assets')),
('assets',['assets/caution.png']),
],
)