Skip to content

Commit ed370ec

Browse files
committed
Clarify Qwen edit VAE conditioning names
1 parent d1fd9f1 commit ed370ec

2 files changed

Lines changed: 71 additions & 28 deletions

File tree

max/python/max/pipelines/lib/pixel_tokenizer.py

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,11 @@
3636
InputImageContent,
3737
InputTextContent,
3838
)
39-
from max.pipelines.architectures.qwen2_5vl.image_processor import (
40-
Qwen2_5VLPromptImageProcessor,
41-
)
4239
from max.pipelines.core import PixelContext
4340
from transformers import AutoTokenizer
4441

4542
from .diffusion_schedulers import SchedulerFactory
43+
from .qwen_image_processor import Qwen2_5VLPromptImageProcessor
4644

4745
if TYPE_CHECKING:
4846
import PIL.Image
@@ -364,6 +362,37 @@ def _randn_tensor(
364362
rng = np.random.RandomState(seed)
365363
return rng.standard_normal(shape).astype(np.float32)
366364

365+
@staticmethod
366+
def _resize_with_center_crop(
367+
image: PIL.Image.Image, target_width: int, target_height: int
368+
) -> PIL.Image.Image:
369+
ratio = target_width / target_height
370+
src_ratio = image.width / image.height
371+
372+
src_w = (
373+
target_width
374+
if ratio > src_ratio
375+
else image.width * target_height // image.height
376+
)
377+
src_h = (
378+
target_height
379+
if ratio <= src_ratio
380+
else image.height * target_width // image.width
381+
)
382+
383+
resized = image.resize(
384+
(src_w, src_h), resample=PIL.Image.Resampling.LANCZOS
385+
)
386+
canvas = PIL.Image.new("RGB", (target_width, target_height))
387+
canvas.paste(
388+
resized,
389+
box=(
390+
target_width // 2 - src_w // 2,
391+
target_height // 2 - src_h // 2,
392+
),
393+
)
394+
return canvas
395+
367396
def _preprocess_input_image(
368397
self,
369398
image: PIL.Image.Image | npt.NDArray[np.uint8],
@@ -372,13 +401,14 @@ def _preprocess_input_image(
372401
) -> PIL.Image.Image:
373402
"""Preprocess input image for image-to-image generation.
374403
375-
This method preprocesses images for condition-based image-to-image generation.
376-
Matching diffusers behavior: resizes large images, ensures dimensions are multiples
377-
of vae_scale_factor * 2, and optionally resizes to target dimensions.
404+
Matches the shared image-to-image behavior by default:
405+
- cap image area when needed
406+
- floor dimensions to multiples of vae_scale_factor * 2
407+
- apply aspect-ratio preserving center-crop resize to the floored size
378408
379-
Note: This is a simplified version compared to pipeline_flux2.py which uses
380-
image_processor.preprocess. This tokenizer-level preprocessing is sufficient
381-
for the Max framework's condition-based approach.
409+
Qwen image edit is the exception. It preserves the requested output
410+
size separately and uses explicit target resize here only for the
411+
condition image branch.
382412
383413
Args:
384414
image: PIL Image or numpy array (uint8) to preprocess.
@@ -388,12 +418,9 @@ def _preprocess_input_image(
388418
Returns:
389419
Preprocessed PIL Image with adjusted dimensions.
390420
"""
391-
import PIL.Image
392-
393421
if isinstance(image, np.ndarray):
394422
image = PIL.Image.fromarray(image.astype(np.uint8))
395423

396-
# Ensure RGB mode (e.g. RGBA PNGs would break 3-channel VAE input)
397424
if image.mode != "RGB":
398425
image = image.convert("RGB")
399426

@@ -412,18 +439,34 @@ def _preprocess_input_image(
412439
)
413440
image_width, image_height = image.size
414441

415-
image_width = (image_width // multiple_of) * multiple_of
416-
image_height = (image_height // multiple_of) * multiple_of
442+
if target_height is None:
443+
image_height = max(
444+
(image_height // multiple_of) * multiple_of, multiple_of
445+
)
446+
else:
447+
image_height = max(
448+
(target_height // multiple_of) * multiple_of, multiple_of
449+
)
417450

418-
if target_height is not None:
419-
image_height = (target_height // multiple_of) * multiple_of
420-
if target_width is not None:
421-
image_width = (target_width // multiple_of) * multiple_of
451+
if target_width is None:
452+
image_width = max(
453+
(image_width // multiple_of) * multiple_of, multiple_of
454+
)
455+
else:
456+
image_width = max(
457+
(target_width // multiple_of) * multiple_of, multiple_of
458+
)
422459

423460
if image.size != (image_width, image_height):
424-
image = image.resize(
425-
(image_width, image_height), PIL.Image.Resampling.LANCZOS
426-
)
461+
if target_height is None and target_width is None:
462+
image = self._resize_with_center_crop(
463+
image, image_width, image_height
464+
)
465+
else:
466+
image = image.resize(
467+
(image_width, image_height),
468+
PIL.Image.Resampling.LANCZOS,
469+
)
427470

428471
return image
429472

@@ -457,11 +500,11 @@ def _prepare_qwen_edit_condition_images(
457500
self._resize_image_to_area(image, QWEN_EDIT_PROMPT_IMAGE_SIZE)
458501
for image in input_images
459502
]
460-
vae_images = [
503+
vae_condition_images = [
461504
self._resize_image_to_area(image, QWEN_EDIT_VAE_IMAGE_SIZE)
462505
for image in input_images
463506
]
464-
return prompt_images, vae_images
507+
return prompt_images, vae_condition_images
465508

466509
def _prepare_qwen_edit_tokens(
467510
self,
@@ -1049,9 +1092,9 @@ async def new_context(
10491092
)
10501093

10511094
prompt_images: list[npt.NDArray[np.uint8]] | None = None
1052-
vae_images: list[npt.NDArray[np.uint8]] | None = None
1095+
vae_condition_images: list[npt.NDArray[np.uint8]] | None = None
10531096
if self._is_qwen_image_edit_family:
1054-
prompt_images, vae_images = (
1097+
prompt_images, vae_condition_images = (
10551098
self._prepare_qwen_edit_condition_images(
10561099
preprocessed_image_arrays
10571100
)
@@ -1134,7 +1177,7 @@ async def new_context(
11341177
model_name=request.body.model,
11351178
input_images=preprocessed_image_arrays,
11361179
prompt_images=prompt_images,
1137-
vae_images=vae_images,
1180+
vae_condition_images=vae_condition_images,
11381181
)
11391182

11401183
for validator in self._context_validators:

max/python/max/pipelines/architectures/qwen2_5vl/image_processor.py renamed to max/python/max/pipelines/lib/qwen_image_processor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212
# ===----------------------------------------------------------------------=== #
13-
"""MAX-native image preprocessing helpers for Qwen image prompt assembly."""
13+
"""MAX-native image preprocessing helpers for Qwen image pipelines."""
1414

1515
from __future__ import annotations
1616

@@ -21,7 +21,6 @@
2121
from max.pipelines.lib import float32_to_bfloat16_as_uint16
2222
from PIL import Image
2323

24-
# Pre-computed normalization constants for ImageNet.
2524
_IMAGENET_MEAN = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32)
2625
_IMAGENET_STD = np.array([0.26862954, 0.26130258, 0.27577711], dtype=np.float32)
2726
_NORM_SCALE = (1.0 / (255.0 * _IMAGENET_STD)).astype(np.float32)
@@ -135,4 +134,5 @@ def preprocess(
135134
return_tensors: str = "np",
136135
**kwargs: Any,
137136
) -> tuple[dict[str, npt.NDArray[Any]], list[npt.NDArray[np.uint16]]]:
137+
"""Alias matching the HuggingFace image processor API."""
138138
return self(images, return_tensors=return_tensors, **kwargs)

0 commit comments

Comments
 (0)