Downloads · 30 days
15
16% of all-time downloads
HudTariq/loomis-model-output-v4-high-quality-7steps
loomis-model-output-v4-high-quality-7steps is a text-to-image model from HudTariq. Use it when you need an image from a text prompt. It is set up for diffusers. The card lists the license as cc-by-nc-4.0.
Downloads · 30 days
15
16% of all-time downloads
All-time downloads
91
Public
Parameters
361M
13.1 GB on disk
Likes
1
Public
Click a slice to open those files.
.safetensors10.1 GB · 70%
From the Hugging Face model README
![]()
Loomis-ControlNet is a custom fine-tuned ControlNet adapter for Stable Diffusion v1.5. It is designed to act as a "virtual art tutor," taking any portrait photograph and deconstructing it into 7 distinct drawing stages based on the Andrew Loomis method—from the initial cranial construction lines to the final graphite rendering.
runwayml/stable-diffusion-v1-5)This model was trained to understand the logical progression of a graphite portrait sketch. By changing the prompt (e.g., step 1, step 4), you can generate:
For the best results—specifically to preserve the identity of your input subject—it is highly recommended to use a Multi-ControlNet pipeline.
Using this model alone creates excellent Loomis-style sketches but may result in a generic face. Pairing it with a Canny or Depth ControlNet "locks" the facial features of your subject.
This script uses the Canny ControlNet to force the AI to trace the exact features of your photo, while the Loomis ControlNet handles the artistic style and construction steps.
import torch
import cv2
import numpy as np
import ipywidgets as widgets
import io
import gc
from PIL import Image
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
from transformers import BlipProcessor, BlipForConditionalGeneration
from IPython.display import display, clear_output
import matplotlib.pyplot as plt
import os
# ==========================================
# 1. LOAD MODELS (Singleton Check)
# ==========================================
def load_models():
global pipe, blip_processor, blip_model
# Only load pipeline if not already loaded
if 'pipe' not in globals() or not isinstance(pipe.controlnet, torch.nn.ModuleList):
print("⏳ Loading ControlNets... (Wait ~1 min)")
cn_loomis = ControlNetModel.from_pretrained(
"HudTariq/loomis-model-output-v4-high-quality-7steps",
subfolder="checkpoint-3000/controlnet",
torch_dtype=torch.float16
)
cn_canny = ControlNetModel.from_pretrained(
"lllyasviel/sd-controlnet-canny",
torch_dtype=torch.float16
)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
controlnet=[cn_loomis, cn_canny],
torch_dtype=torch.float16,
safety_checker=None
).to("cuda")
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
print("✅ Pipeline Loaded!")
# Only load captioner if not already loaded
if 'blip_model' not in globals():
print("⏳ Loading Captioner...")
blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base")
blip_model = BlipForConditionalGeneration.from_pretrained(
"Salesforce/blip-image-captioning-base",
torch_dtype=torch.float16
).to("cuda")
print("✅ Captioner Loaded!")
load_models()
# ==========================================
# 2. ROBUST FACE CROPPER (OpenCV Version)
# ==========================================
def smart_crop(image):
"""Detects face using OpenCV and crops to portrait."""
# Download the Face Cascade XML if missing (Standard OpenCV model)
cascade_path = "haarcascade_frontalface_default.xml"
if not os.path.exists(cascade_path):
os.system(f"wget -q https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/{cascade_path}")
# Convert PIL -> OpenCV
img_np = np.array(image)
gray = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
# Detect Faces
face_cascade = cv2.CascadeClassifier(cascade_path)
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
if len(faces) == 0:
print("⚠️ No face detected (or face too small). Using Center Crop.")
width, height = image.size
short_dim = min(width, height)
# Fallback: simple square crop
return image.crop(((width-short_dim)//2, (height-short_dim)//2, (width+short_dim)//2, (height+short_dim)//2)).resize((512,512))
# Pick the largest face found
x, y, w, h = max(faces, key=lambda b: b[2] * b[3])
# === THE MAGIC PADDING MATH ===
# Loomis portraits need the head to take up about 50-60% of vertical space
# We expand the crop box by 2.2x the face size
crop_size = int(max(w, h) * 2.2)
center_x = x + w // 2
center_y = y + h // 2
img_h, img_w = img_np.shape[:2]
x1 = max(0, center_x - crop_size // 2)
y1 = max(0, center_y - crop_size // 2)
x2 = min(img_w, center_x + crop_size // 2)
y2 = min(img_h, center_y + crop_size // 2)
# Crop and Resize
cropped = image.crop((x1, y1, x2, y2))
return cropped.resize((512, 512), Image.LANCZOS)
# ==========================================
# 3. HELPERS
# ==========================================
def get_canny_image(image, low_threshold=100, high_threshold=200):
image = np.array(image)
image = cv2.Canny(image, low_threshold, high_threshold)
image = image[:, :, None]
image = np.concatenate([image, image, image], axis=2)
return Image.fromarray(image)
def auto_detect_subject(image):
inputs = blip_processor(image, "a close up photo of a", return_tensors="pt").to("cuda", torch.float16)
out = blip_model.generate(**inputs, max_new_tokens=20)
caption = blip_processor.decode(out[0], skip_special_tokens=True)
return caption.replace("a close up photo of a", "").strip()
# ==========================================
# 4. EXECUTION LOGIC
# ==========================================
STEP_PROMPTS = {
1: "step 1, basic cranial circle, jawline stroke, ear axis line, raw construction, minimal lines",
2: "step 2, profile contour, nose silhouette, lip profile, chin outline, wireframe",
3: "step 3, ear shape definition, eye socket placement, nostril marking, feature blocking",
4: "step 4, side plane shading, cheekbone shadow, temple value, planar masses",
5: "step 5, hair mass blocking, mid-tone shading, ear detailing, form definition",
6: "step 6, hair strand texture, stubble detail, skin texture, eye shading, contrast",
7: "step 7, final graphite render, deep shadows, hyper-realistic detailed portrait, masterpiece"
}
STRENGTH_SCHEDULE = {1: 0.30, 2: 0.40, 3: 0.55, 4: 0.65, 5: 0.80, 6: 0.90, 7: 1.00}
uploader = widgets.FileUpload(accept='image/*', multiple=False, description='Upload')
subject_display = widgets.Text(placeholder='Detected subject...', description='Subject:', disabled=True)
btn_gen = widgets.Button(description='Smart Crop & Generate', button_style='primary', icon='crop', layout=widgets.Layout(width='100%'))
out = widgets.Output()
def run_generation(b):
with out:
clear_output()
if not uploader.value:
print("⚠️ Upload a photo!")
return
try:
val = uploader.value[0] if isinstance(uploader.value, tuple) else list(uploader.value.values())[0]
content = val['content']
except:
print("⚠️ Upload Error. Try again.")
return
print("🔍 Detecting Face (OpenCV) & Cropping...")
raw_img = Image.open(io.BytesIO(content)).convert("RGB")
input_image = smart_crop(raw_img)
display(input_image.resize((150,150))) # Show crop preview
print("👁️ Captioning...")
subject = auto_detect_subject(input_image)
subject_display.value = subject
print(f"✅ Subject: {subject}")
canny_image = get_canny_image(input_image)
images = [input_image]
titles = ["Original Crop"]
print("🎨 Sketching...")
for i in range(1, 8):
prompt = f"a Loomis construction sketch of a {subject}, {STEP_PROMPTS[i]}"
strength = STRENGTH_SCHEDULE[i]
generator = torch.Generator(device="cuda").manual_seed(42)
with torch.no_grad():
res = pipe(
prompt,
image=[input_image, canny_image],
num_inference_steps=25,
guidance_scale=7.5,
controlnet_conditioning_scale=[1.0, strength],
generator=generator
).images[0]
images.append(res)
titles.append(f"Step {i}")
torch.cuda.empty_cache()
fig, axes = plt.subplots(1, 8, figsize=(24, 4))
for ax, img, title in zip(axes, images, titles):
ax.imshow(img)
ax.set_title(title, fontsize=10)
ax.axis('off')
plt.tight_layout()
plt.show()
gc.collect()
btn_gen.on_click(run_generation)
display(widgets.VBox([widgets.Label("Loomis Portrait Generator (Auto-Crop)"), uploader, subject_display, btn_gen, out])
To get the most authentic "tutorial" look, adjust the Canny ControlNet strength based on the step you are generating:
The model responds to specific keywords associated with the training data.
basic cranial circle (Step 1)wireframe, chin outline (Step 2)planar masses, cheekbone shadow (Step 4)graphite render, masterpiece (Step 7)This model is released under the CC BY-NC 4.0 license.