def visualize_intrinsics(
        prediction: Union[
            np.ndarray,
            torch.Tensor,
            List[np.ndarray],
            List[torch.Tensor],
        ],
        target_properties: Dict[str, Any],
        color_map: Union[str, Dict[str, str]] = "binary",
    ) -> List[Dict[str, PIL.Image.Image]]:
        """
        Visualizes intrinsic image decomposition, such as predictions of the `MarigoldIntrinsicsPipeline`.

        Args:
            prediction (`Union[np.ndarray, torch.Tensor, List[np.ndarray], List[torch.Tensor]]`):
                Intrinsic image decomposition.
            target_properties (`Dict[str, Any]`):
                Decomposition properties. Expected entries: `target_names: List[str]` and a dictionary with keys
                `prediction_space: str`, `sub_target_names: List[Union[str, Null]]` (must have 3 entries, null for
                missing modalities), `up_to_scale: bool`, one for each target and sub-target.
            color_map (`Union[str, Dict[str, str]]`, *optional*, defaults to `"Spectral"`):
                Color map used to convert a single-channel predictions into colored representations. When a dictionary
                is passed, each modality can be colored with its own color map.

        Returns: `List[Dict[str, PIL.Image.Image]]` with intrinsic image decomposition visualization.
        """
        if "target_names" not in target_properties:
            raise ValueError("Missing `target_names` in target_properties")
        if not isinstance(color_map, str) and not (
            isinstance(color_map, dict)
            and all(isinstance(k, str) and isinstance(v, str) for k, v in color_map.items())
        ):
            raise ValueError("`color_map` must be a string or a dictionary of strings")
        n_targets = len(target_properties["target_names"])

        def visualize_targets_one(images, idx=None):
            # img: [T, 3, H, W]
            out = {}
            for target_name, img in zip(target_properties["target_names"], images):
                img = img.permute(1, 2, 0)  # [H, W, 3]
                prediction_space = target_properties[target_name].get("prediction_space", "srgb")
                if prediction_space == "stack":
                    sub_target_names = target_properties[target_name]["sub_target_names"]
                    if len(sub_target_names) != 3 or any(
                        not (isinstance(s, str) or s is None) for s in sub_target_names
                    ):
                        raise ValueError(f"Unexpected target sub-names {sub_target_names} in {target_name}")
                    for i, sub_target_name in enumerate(sub_target_names):
                        if sub_target_name is None:
                            continue
                        sub_img = img[:, :, i]
                        sub_prediction_space = target_properties[sub_target_name].get("prediction_space", "srgb")
                        if sub_prediction_space == "linear":
                            sub_up_to_scale = target_properties[sub_target_name].get("up_to_scale", False)
                            if sub_up_to_scale:
                                sub_img = sub_img / max(sub_img.max().item(), 1e-6)
                            sub_img = sub_img ** (1 / 2.2)
                        cmap_name = (
                            color_map if isinstance(color_map, str) else color_map.get(sub_target_name, "binary")
                        )
                        sub_img = MarigoldImageProcessor.colormap(sub_img, cmap=cmap_name, bytes=True)
                        sub_img = PIL.Image.fromarray(sub_img.cpu().numpy())
                        out[sub_target_name] = sub_img
                elif prediction_space == "linear":
                    up_to_scale = target_properties[target_name].get("up_to_scale", False)
                    if up_to_scale:
                        img = img / max(img.max().item(), 1e-6)
                    img = img ** (1 / 2.2)
                elif prediction_space == "srgb":
                    pass
                img = (img * 255).to(dtype=torch.uint8, device="cpu").numpy()
                img = PIL.Image.fromarray(img)
                out[target_name] = img
            return out

        if prediction is None or isinstance(prediction, list) and any(o is None for o in prediction):
            raise ValueError("Input prediction is `None`")
        if isinstance(prediction, (np.ndarray, torch.Tensor)):
            prediction = MarigoldImageProcessor.expand_tensor_or_array(prediction)
            if isinstance(prediction, np.ndarray):
                prediction = MarigoldImageProcessor.numpy_to_pt(prediction)  # [N*T,3,H,W]
            if not (prediction.ndim == 4 and prediction.shape[1] == 3 and prediction.shape[0] % n_targets == 0):
                raise ValueError(f"Unexpected input shape={prediction.shape}, expecting [N*T,3,H,W].")
            N_T, _, H, W = prediction.shape
            N = N_T // n_targets
            prediction = prediction.reshape(N, n_targets, 3, H, W)
            return [visualize_targets_one(img, idx) for idx, img in enumerate(prediction)]
        elif isinstance(prediction, list):
            return [visualize_targets_one(img, idx) for idx, img in enumerate(prediction)]
        else:
            raise ValueError(f"Unexpected input type: {type(prediction)}")

        Visualizes intrinsic image decomposition, such as predictions of the `MarigoldIntrinsicsPipeline`.

        Args:
            prediction (`Union[np.ndarray, torch.Tensor, List[np.ndarray], List[torch.Tensor]]`):
                Intrinsic image decomposition.
            target_properties (`Dict[str, Any]`):
                Decomposition properties. Expected entries: `target_names: List[str]` and a dictionary with keys
                `prediction_space: str`, `sub_target_names: List[Union[str, Null]]` (must have 3 entries, null for
                missing modalities), `up_to_scale: bool`, one for each target and sub-target.
            color_map (`Union[str, Dict[str, str]]`, *optional*, defaults to `"Spectral"`):
                Color map used to convert a single-channel predictions into colored representations. When a dictionary
                is passed, each modality can be colored with its own color map.

        Returns: `List[Dict[str, PIL.Image.Image]]` with intrinsic image decomposition visualization.
        
src/diffusers/pipelines/marigold/marigold_image_processing.py