Log in

View Full Version : How do I import image with a color profile?


lansing
12th March 2020, 22:07
My tiff image has an color profile "Adobe RGB (1998)", and when I import it to vs, the program discarded the profile and displayed the wrong color. What do I do?


clip = core.imwri.Read(file)

poisondeathray
12th March 2020, 22:28
vapoursynth, avisynth, ffmpeg etc... do not support icc color profiles

You can use imagemagick's convert.exe to batch convert to sRGB

eg as a windows batch file (use one % instead of two if executing from command line)

for %%a in ("*.tiff") do "D:\ImageMagick\convert" "%%a" -colorspace sRGB -profile "D:\ICC color profiles\sRGB2014.icc" "OUTPUTPATH\%%~na.png"
pause

lansing
13th March 2020, 04:07
vapoursynth, avisynth, ffmpeg etc... do not support icc color profiles

You can use imagemagick's convert.exe to batch convert to sRGB

eg as a windows batch file (use one % instead of two if executing from command line)

for %%a in ("*.tiff") do "D:\ImageMagick\convert" "%%a" -colorspace sRGB -profile "D:\ICC color profiles\sRGB2014.icc" "OUTPUTPATH\%%~na.png"
pause


That's one hassle that I wanted to avoid. I read that Adobe Premiere does the conversion automatically when importing images.

poisondeathray
13th March 2020, 05:07
That's one hassle that I wanted to avoid. I read that Adobe Premiere does the conversion automatically when importing images.

All color managed programs can handle it automatically

_Al_
13th March 2020, 05:59
looking at this link,
https://stackoverflow.com/questions/33168615/how-to-read-jpeg-image-with-adobe-rgb-colorspace-in-opencv
if that iccp profile is bytes pulled from that info, you could use just Python using PIL and numpy modules:
import PIL
import PIL.ImageCms
import numpy as np
import functools

clip_placeholder = core.std.BlankClip(width = 800, height = 605, format= vs.RGB24, length=1)
rgbp = PIL.ImageCms.createProfile("sRGB")
im = PIL.Image.open(r'D:\Photos\5102_3493.jpg')

def get_clip(n, f, im):
try:
iccp = im.info['icc_profile']
except KeyError as e:
#no icc_profile in image
return f
icc2rgb = PIL.ImageCms.buildTransformFromOpenProfiles(rgbp, iccp, "RGB", "RGB")
im = PIL.ImageCms.applyTransform(im, icc2rgb)
npArray = np.array(im)
vsFrame = f.copy()
[np.copyto(np.asarray(vsFrame.get_write_array(i)), npArray[:, :, i]) for i in range(3)]
return vsFrame

clip = core.std.ModifyFrame(clip_placeholder, clip_placeholder, functools.partial(get_clip, im=im))
clip.set_output()
ok this way it is without error, but still not sure if that iccp is correct bytes type

lansing
13th March 2020, 07:08
I'm getting a black frame from the script

Now when I pass in a jpg with the AdobeRGB 1998 icc profile, I'm getting "Invalid type for Profile"

_Al_
13th March 2020, 07:46
if image needs to be passed without profile it would need to be:
def get_clip(n, f, im):
vsFrame = f.copy()
try:
iccp = im.info['icc_profile']
except KeyError as e:
#no profile pass image to vs as is
[np.copyto(np.asarray(vsFrame.get_write_array(i)), np.array(im)[:, :, i]) for i in range(3)]
return vsFrame
#other part if profile is present ...
there might be a type problem , wrong type of iccp input for that buildTransform function, maybe it expects a file, so file needs to be written from those iccp bytes
or if you have that one, you can use that line to create that iccp:
iccp = PIL.ImageCms.getOpenProfile("profile.icc")

Myrsloik
13th March 2020, 07:54
Create an issue and I'll see if I can add support later.

lansing
13th March 2020, 08:15
Create an issue and I'll see if I can add support later.

Thank you

lansing
13th March 2020, 08:31
if image needs to be passed without profile it would need to be:
def get_clip(n, f, im):
vsFrame = f.copy()
try:
iccp = im.info['icc_profile']
except KeyError as e:
#no profile pass image to vs as is
[np.copyto(np.asarray(vsFrame.get_write_array(i)), np.array(im)[:, :, i]) for i in range(3)]
return vsFrame
#other part if profile is present ...
there might be a type problem , wrong type of iccp input for that buildTransform function, maybe it expects a file, so file needs to be written from those iccp bytes
or if you have that one, you can use that line to create that iccp:
iccp = PIL.ImageCms.getOpenProfile("profile.icc")

Thanks, I got it working.

I matched the blankclip resolution to the size of the image, loaded the AdobeRGB icc profile to the "iccp", switch place the inputProfile/outputProfile in the buildTransformFromOpenProfiles function. And it works.

poisondeathray
13th March 2020, 15:36
Thanks, I got it working.

I matched the blankclip resolution to the size of the image, loaded the AdobeRGB icc profile to the "iccp", switch place the inputProfile/outputProfile in the buildTransformFromOpenProfiles function. And it works.

Color seems off. I might be doing something wrong

Can you provide the full script and/or more clear instructions?

EDIT: nevermind, stupid mistake, it works


Thanks _Al_ and lansing .

poisondeathray
13th March 2020, 16:16
I'm having difficulty loading an image sequence with PIL, any pointers ?

It seems like ImageSequence.Iterator should work, but I can't get it to work

https://pillow.readthedocs.io/en/3.0.x/reference/ImageSequence.html

Or is there a way to use imwri.Read to load the sequence, then apply the function? I couldn't get that to work either

_Al_
13th March 2020, 17:43
To load sequence of images,
Python could be used to get a list of your images, and then pass it in ModifyFrame instead of one image. You can select tupple of extensions, if more , it should load properly if named with some increase number pattern, you need import os as well, blankclip should match dimensions and length depending how many images there is, set fps numerator and fps denominator:

import PIL
import PIL.ImageCms
import numpy as np
import functools
import os

ext = (".jpg", ".tiff")
directory = r'G:\Photos\'

image_path_list = []
for file in os.listdir(directory):
if file.lower().endswith(tuple(ext)):
image_path = os.path.join(directory, file)
image_path_list.append(image_path)
clip_placeholder = core.std.BlankClip(width = 3648, height = 2736, fpsnum=60000, fpsden=1001, format= vs.RGB24, length=len(image_path_list))
rgbp = PIL.ImageCms.createProfile("sRGB")


def get_clip(n, f, image_path_list):
im = PIL.Image.open(image_path_list[n])
vsFrame = f.copy()
try:
iccp = im.info['icc_profile']
except KeyError as e:
npArray = np.array(im)
[np.copyto(np.asarray(vsFrame.get_write_array(i)), npArray[:, :, i]) for i in range(3)]
return vsFrame
#other part that treats if profile present

clip = core.std.ModifyFrame(clip_placeholder, clip_placeholder, functools.partial(get_clip, image_path_list=image_path_list))
clip.set_output()

also name for that function get_clip is misleading, it rather should be get_frame, to invoke what it returns, especially if working with images, that only one frame is returned using ModifyFrame, not clip

_Al_
13th March 2020, 18:43
Using imwri.Read creates a clip right? So that would be your clip_placeholder in your case and within that function, it would need to be changed to numpy, then, PIL's format:

clip_placeholder = clip #should be RGB
def get_clip(n, f):
vsFrame = f.copy()
npArray = np.dstack([np.array(f.get_read_array(i), copy=False) for i in range(3)])
im = PIL.Image.fromarray(npArray, 'RGB')
#.....
clip = core.std.ModifyFrame(clip_placeholder, clip_placeholder, get_clip)
clip.set_output()

age
15th March 2020, 01:54
It wouldn't be easier to use fmtconv and manually do the conversion inside vapoursynth?

poisondeathray
15th March 2020, 02:25
It wouldn't be easier to use fmtconv and manually do the conversion inside vapoursynth?

Yes.

And, I did try this before, with ProPhoto and AdobeRGB... but wrong colors...

I just revisited it, and it turns out it does work. I just did it incorrectly before.

The "trick" is to linearize the transfer function first, before applying the primaries correction, then adjusting the transfer to srgb



.
.
.
clip = core.fmtc.bitdepth (clip, bits=32)
clip = core.fmtc.transfer (clip, transs="adobergb", transd="linear")
clip = core.fmtc.primaries (clip, prims="adobe98", primd="srgb")
clip = core.fmtc.transfer (clip, transs="linear", transd="srgb")
#clip = core.fmtc.bitdepth (clip, bits=8)
clip.set_output()

lansing
15th March 2020, 05:20
Yes.

And, I did try this before, with ProPhoto and AdobeRGB... but wrong colors...

I just revisited it, and it turns out it does work. I just did it incorrectly before.

The "trick" is to linearize the transfer function first, before applying the primaries correction, then adjusting the transfer to srgb



.
.
.
clip = core.fmtc.bitdepth (clip, bits=32)
clip = core.fmtc.transfer (clip, transs="adobergb", transd="linear")
clip = core.fmtc.primaries (clip, prims="adobe98", primd="srgb")
clip = core.fmtc.transfer (clip, transs="linear", transd="srgb")
#clip = core.fmtc.bitdepth (clip, bits=8)
clip.set_output()



Memory spikes very high when doing this. On my 4200 x 3400 image, ModifyFrame uses 200 MB RAM while this one uses 860 MB.

poisondeathray
15th March 2020, 07:10
Memory spikes very high when doing this. On my 4200 x 3400 image, ModifyFrame uses 200 MB RAM while this one uses 860 MB.

You can use less memory when not using RGBS

The fmtc conversion is slightly more accurate for whatever reason even at RGB24 or RGB48 , but still consumes more memory

But at least there are more options, and image sequence loading is easier with imwri.Read