Strange red curve after calibration

Home Forums Help and Support Strange red curve after calibration

Viewing 15 posts - 16 through 30 (of 64 total)
  • Author
    Posts
  • #144030

    infradragon
    Participant
    • Offline

    For future reference, this is the DisplayCAL logic for the colprof command line

     cmd = get_argyll_util("colprof")
    args = ["-v", f"-q{getcfg('profile.quality')}", f"-a{getcfg('profile.type')}"]
    gamap_args = args
    if getcfg("profile.type") in ["l", "x", "X"]:
    if getcfg("gamap_saturation"):
    gamap = "S"
    elif getcfg("gamap_perceptual"):
    gamap = "s"
    else:
    gamap = None
    gamap_profile = None
    if gamap and getcfg("gamap_profile"):
    # CIECAM02 gamut mapping - perceptual and saturation tables
    # Only for L*a*b* LUT or if source profile is not a simple matrix
    # profile, otherwise create hires CIECAM02 tables with collink
    try:
    gamap_profile = ICCProfile(getcfg("gamap_profile"))
    except ICCProfileInvalidError as exception:
    self.log(exception)
    return Error(
    lang.getstr("profile.invalid") + "\n" + getcfg("gamap_profile")
    )
    except IOError as exception:
    return exception
    if (
    getcfg("profile.type") != "l"
    and getcfg("profile.b2a.hires")
    and "A2B0" not in gamap_profile.tags
    and "rXYZ" in gamap_profile.tags
    and "gXYZ" in gamap_profile.tags
    and "bXYZ" in gamap_profile.tags
    and "rTRC" in gamap_profile.tags
    and "gTRC" in gamap_profile.tags
    and "bTRC" in gamap_profile.tags
    ):
    self.log("Delegating CIECAM02 gamut mapping to collink")
    # Make a copy, so we can store options without adding them
    # to actual colprof arguments
    gamap_args = []
    gamap_profile = None
    gamap_args.append(f"-{gamap}")
    gamap_args.append(getcfg("gamap_profile"))
    gamap_args.append(f"-t{getcfg('gamap_perceptual_intent')}")
    if gamap == "S":
    gamap_args.append(f"-T{getcfg('gamap_saturation_intent')}")
    if getcfg("gamap_src_viewcond"):
    gamap_args.append(f"-c{getcfg('gamap_src_viewcond')}")
    if getcfg("gamap_out_viewcond"):
    gamap_args.append(f"-d{getcfg('gamap_out_viewcond')}")
    b2a_q = getcfg("profile.quality.b2a")
    if (
    getcfg("profile.b2a.hires")
    and getcfg("profile.type") in ("l", "x", "X")
    and not (gamap and gamap_profile)
    ):
    rgb = False
    is_lab_clut_ptype = getcfg("profile.type") == "l"
    if is_lab_clut_ptype:
    with open(f"{in_out_file}.ti3", "rb") as ti3_file:
    for line in ti3_file:
    if line.startswith(b"COLOR_REP"):
    if b"RGB_XYZ" in line:
    rgb = True
    break
    if rgb or not is_lab_clut_ptype:
    # Disable B2A creation in colprof, B2A is handled
    # by A2B inversion code (only for cLUT profiles)
    b2a_q = "n"
    if b2a_q and b2a_q != getcfg("profile.quality"):
    args.append(f"-b{b2a_q}")
    args.append("-C")
    args.append(getcfg("copyright").encode("ASCII", "asciize").decode("utf-8"))
    if getcfg("extra_args.colprof").strip():
    args += parse_argument_string(getcfg("extra_args.colprof"))
    options_dispcal = []
    if "-d3" in self.options_targen:
    # only add display desc and dispcal options if creating RGB profile
    options_dispcal = self.options_dispcal
    if len(self.displays):
    args.extend(
    self.update_display_name_manufacturer(
    f"{in_out_file}.ti3",
    display_name,
    display_manufacturer,
    write=False,
    )
    )
    self.options_colprof = list(args)
    if gamap_args is not args:
    self.options_colprof.extend(gamap_args)
    args.append("-D")
    args.append(profile_name)
    args.append(in_out_file)
    # Add dispcal and colprof arguments to ti3
    ti3 = add_options_to_ti3(
    f"{in_out_file}.ti3", options_dispcal, self.options_colprof
    )
    if ti3:
    color_rep = (ti3.queryv1("COLOR_REP").decode("utf-8") or "").split("_")
    # Prepare ChromaticityType tag
    self.log("Preparing ChromaticityType tag from TI3 colorants")
    colorants = ti3.get_colorants()
    if colorants and None not in colorants:
    chrm = ChromaticityType()
    chrm.type = 0
    for colorant in colorants:
    if color_rep[1] == "LAB":
    XYZ = colormath.Lab2XYZ(
    colorant["LAB_L"], colorant["LAB_A"], colorant["LAB_B"]
    )
    else:
    XYZ = (colorant["XYZ_X"], colorant["XYZ_Y"], colorant["XYZ_Z"])
    chrm.channels.append(colormath.XYZ2xyY(*XYZ)[:-1])
    with open(in_out_file + ".chrm", "wb") as blob:
    blob.write(chrm.tagData)
    self.log("Storing settings in TI3")
    # Black point compensation
    ti3[0].add_keyword(
    "USE_BLACK_POINT_COMPENSATION",
    "YES" if getcfg("profile.black_point_compensation") else "NO",
    )
    # Black point correction
    # NOTE that profile black point correction is not the same as
    # calibration black point correction!
    # See Worker.create_profile
    ti3[0].add_keyword(
    "BLACK_POINT_CORRECTION", getcfg("profile.black_point_correction")
    )
    # Hires B2A with optional smoothing
    ti3[0].add_keyword(
    "HIRES_B2A", "YES" if getcfg("profile.b2a.hires") else "NO"
    )
    ti3[0].add_keyword("HIRES_B2A_SIZE", getcfg("profile.b2a.hires.size"))
    ti3[0].add_keyword(
    "SMOOTH_B2A", "YES" if getcfg("profile.b2a.hires.smooth") else "NO"
    )
    # Display update delay
    if getcfg("measure.override_min_display_update_delay_ms"):
    ti3[0].add_keyword(
    "MIN_DISPLAY_UPDATE_DELAY_MS",
    getcfg("measure.min_display_update_delay_ms"),
    )
    # Display settle time multiplier
    if getcfg("measure.override_display_settle_time_mult"):
    ti3[0].add_keyword(
    "DISPLAY_SETTLE_TIME_MULT",
    getcfg("measure.display_settle_time_mult"),
    )
    # FFP
    if getcfg("patterngenerator.ffp_insertion"):
    for keyword in ("INTERVAL", "DURATION", "LEVEL"):
    ti3[0].add_keyword(
    f"FFP_INSERTION_{keyword}",
    getcfg(f"patterngenerator.ffp_insertion.{keyword.lower()}"),
    )
    # Remove AUTO_OPTIMIZE
    if ti3[0].queryv1("AUTO_OPTIMIZE"):
    ti3[0].remove_keyword("AUTO_OPTIMIZE")
    # Patch sequence
    ti3[0].add_keyword(
    "PATCH_SEQUENCE", getcfg("testchart.patch_sequence").upper()
    )
    # Add 3D LUT options if set, else remove them
    for keyword, cfgname in {
    "3DLUT_SOURCE_PROFILE": "3dlut.input.profile",
    "3DLUT_TRC": "3dlut.trc",
    "3DLUT_HDR_PEAK_LUMINANCE": "3dlut.hdr_peak_luminance",
    "3DLUT_HDR_SAT": "3dlut.hdr_sat",
    "3DLUT_HDR_HUE": "3dlut.hdr_hue",
    "3DLUT_HDR_MAXMLL": "3dlut.hdr_maxmll",
    "3DLUT_HDR_MAXMLL_ALT_CLIP": "3dlut.hdr_maxmll_alt_clip",
    "3DLUT_HDR_MINMLL": "3dlut.hdr_minmll",
    "3DLUT_HDR_AMBIENT_LUMINANCE": "3dlut.hdr_ambient_luminance",
    "3DLUT_HDR_DISPLAY": "3dlut.hdr_display",
    "3DLUT_GAMMA": "3dlut.trc_gamma",
    "3DLUT_DEGREE_OF_BLACK_OUTPUT_OFFSET": "3dlut.trc_output_offset",
    "3DLUT_INPUT_ENCODING": "3dlut.encoding.input",
    "3DLUT_OUTPUT_ENCODING": "3dlut.encoding.output",
    "3DLUT_GAMUT_MAPPING_MODE": "3dlut.gamap.use_b2a",
    "3DLUT_RENDERING_INTENT": "3dlut.rendering_intent",
    "3DLUT_FORMAT": "3dlut.format",
    "3DLUT_SIZE": "3dlut.size",
    "3DLUT_INPUT_BITDEPTH": "3dlut.bitdepth.input",
    "3DLUT_OUTPUT_BITDEPTH": "3dlut.bitdepth.output",
    "3DLUT_APPLY_CAL": "3dlut.output.profile.apply_cal",
    "SIMULATION_PROFILE": "measurement_report.simulation_profile",
    }.items():
    if getcfg("3dlut.create"):
    value = getcfg(cfgname)
    if cfgname == "3dlut.gamap.use_b2a":
    if value:
    value = "g"
    else:
    value = "G"
    elif cfgname == "3dlut.trc_gamma":
    if getcfg("3dlut.trc_gamma_type") == "B":
    value = -value
    elif (
    cfgname == "3dlut.input.profile"
    and os.path.basename(os.path.dirname(value)) == "ref"
    and get_data_path("ref/" + os.path.basename(value)) == value
    ):
    # Store relative path instead of absolute path if
    # ref file
    value = "ref/" + os.path.basename(value)
    elif cfgname == "measurement_report.simulation_profile":
    if (
    getcfg("3dlut.trc").startswith("smpte2084")
    or getcfg("3dlut.trc") == "hlg"
    ):
    # Use 3D LUT profile
    value = getcfg("3dlut.input.profile")
    # Add 3D LUT HDR parameters and store only filename
    # (file will be copied to profile dir)
    fn, ext = os.path.splitext(os.path.basename(value))
    lut3d_fn = self.lut3d_get_filename(fn, False, False)
    value = lut3d_fn + ext
    else:
    value = None
    else:
    value = None
    if value is not None:
    ti3[0].add_keyword(keyword, safe_str(value, "utf-8"))
    elif keyword in ti3[0]:
    ti3[0].remove_keyword(keyword)
    # 3D LUT content color space (currently only used for HDR)
    for color in ("white", "red", "green", "blue"):
    for coord in "xy":
    keyword = "3DLUT_CONTENT_COLORSPACE_{}_{}".format(
    color.upper(),
    coord.upper(),
    )
    if getcfg("3dlut.create"):
    value = getcfg(
    "3dlut.content.colorspace.{}.{}".format(color, coord)
    )
    ti3[0].add_keyword(keyword, safe_str(value, "utf-8"))
    elif keyword in ti3[0]:
    ti3[0].remove_keyword(keyword)
    ti3[0].fix_zero_measurements(logfile=self.get_logfiles(False))
    ti3.write()
    return cmd, args
    #144031

    infradragon
    Participant
    • Offline

    I wasn’t able to get DisplayCAL to dump options_colprof because of the dbus error causing an early exit, but I was able to get a debug log of the crash

    I’ll open a bug report since this is definitely a DisplayCAL bug

    Attachments:
    You must be logged in to view attached files.
    #144038

    infradragon
    Participant
    • Offline

    Just to recap so far, there are now two identified issues, one caused by DisplayCAL crashing and making it impossible to test new profiles without spending 6 hours on it and another one that may or may not be caused by how KDE interprets my ICC profiles, making my colors weird.

    • This reply was modified 10 months ago by infradragon.
    #144043

    Vincent
    Participant
    • Offline

    I’m not sure how my screenshots could be wrong if they match what my eyes see. Unless your display is improperly color managed, (which would be ironic), you can see the cyan tint in the highlights as well as the jump in lightness at the very top of the red channel.

    You cannot use color managed apps to test VCGT issues because there is an additional transformstion.
    You are testing the results of the full pipeline, not just VCGT, hence your test are not valid.

    “CALIBRATION” (“gamma” & grey coloration) issues must be tested visually non color managed.

    Non color-managed apps expect your display to be the same as the developer’s which is almost never the case, therefore I would prefer to be using a tone curve that is correct according to my tastes.

    Non color managed allow you to test color tint in greys, VCGT posterization/banding, and black/white clipping DONE BY VCGT & GPU driver. Alone, no other transformation on pipeline.

    I couldn’t get the DisplayCAL to work in the Windows vm with the VirtIO GPU, so i tried running ArgyllCMS from the command line with DisplayCAL’s default options and it still failed to create the icc profile, and the log didn’t look any different.

    AFAIK there is no way to bypass VCGT from guest to host, other than copying .cal and applying it to host manually.
    But this test may be unreliable too if (IDNK) VM is foing some kind of forced sRGB simulation, hence applying more stept to te pipeline and thus being an unreliable test.

    The dbus error was also nowhere to be seen so I can confirm that was a red herring. I then tried to do a simple gray curve and then a simple gamma correction, and they both failed in the same way, with nothing new in the logs. dispread simply omits the ICC profile from the output directory without any obvious indication as to why, and they’ve only ever done this when I don’t use 11k patches.

    AFAIK new displaycal (3.9) was tested by a lot of users on macOS, less on Linux and WIndows, so it may be possible to have some integration errors with the miriad of desktop enviroments in Linux… but ArgyllCMS should work to create a .cal file with VCGT and then applying it manually with dispwin (I think it ts thsi tool, but i woudl need to double check documentation)

    • This reply was modified 10 months ago by Vincent.
    • This reply was modified 10 months ago by Vincent.
    #144046

    infradragon
    Participant
    • Offline

    I see how a color managed app would make it difficult to test things, I just opened a test image in basically every app I have and discovered that 99% of the programs I have are color managed, even ones I wouldn’t expect to be, like okular and dolphin. I used chafa to print the image in my terminal in the kitty format which I guess isnt color managed and it appeared normally (big sigh of relief) which means something that all of my color managed apps are doing is causing the discoloration.

    #144049

    Vincent
    Participant
    • Offline

    GIMP has a “no color management” configuration, but I’m not sure if it is working on Linux+Wayland+whatever EDID primaries to sRGB mapping it is applying.
    Try it with 255 Red and green patches if your didplay gamut is significatively bigger than sRGB. If it shows oversaturated green red, it seems CM is disabled and ypu can do visual tests.

    #144051

    infradragon
    Participant
    • Offline

    I know for a fact that there are no EDID primaries in play, because when I ordered my laptop’s LCD panel from the factory it had an incorrect EDID metadata block and I had to rewrite it from scratch.

    It look’s like Gimp’s “no color management” setting works, which will make testing easier, thanks for pointing that out. After testing the same gradient with that setting back-to-back I noticed that disabling color management made the situation better, but it did not go away completely. In addition, in color managed mode the issue seems to happen regardless of the test image’s color profile. There is a bit of red/orange near black and I didn’t think that was real issue since DisplayCAL turned off black point correction automatically because I have an LCD with a good contrast ratio, but when disabling color management it gets far less noticeable.
    The boosted red channel near black and the low red channel near white seem to be connected, as they are affected in the same way by disabling color management.

    Since I now know what to look for and how to test it reliably, I sent my laptop’s ICC profile over to my main workstation which is running Windows 11 and applied the ICC profile and it looked completely normal and smooth, like it did with the other laptop running Windows 10 I calibrated. No oddities near the black and white points. it was a little cool overall, but that’s because my laptop’s display is very warm.

    Now that I know for certain that the issue is with how KDE is interpreting the ICC profile, I just need to find a format that KDE likes. I know one exists because the synthetic ICC profile I generated just to manually correct my gamma does not have any of these oddities.
    All I know for certain right now is that KDE wants an XYZ connection space and will not accept an ICC profile without primaries, as it told me those two things explicitly when I was testing different formats with colprof.

    I would start with DisplayCAL’s colprof options and modify from there, but due to the second bug I found earlier I can’t get DisplayCAL to continue to the part of the python code that generates options_colprof and dumps them in debug mode without waiting 6 or so hours for a profile. If I can’t fix DisplayCAL myself (I’m not expecting my bug report to get answered for quite a while) I’ll have to just dump them overnight.

    Attachments:
    You must be logged in to view attached files.
    #144055

    infradragon
    Participant
    • Offline

    If someone could run DisplayCAL in debug mode from the python3 console and calibrate a display using the preset laptop mode and send me the part of their log that says:
    [D] options_colprof:
    I would really appreciate it.

    You can open the console by typing “python3” in a terminal or command prompt, and then type:

    from DisplayCAL.main import main, debug
    debug = true
    main()
    #144056

    DaniJ
    Participant
    • Offline

    Have you also verified the LUTs stored inside the profile? I can take a look later if needed

    #144057

    infradragon
    Participant
    • Offline

    I’m not entirely sure what you mean but I have run “Check measurement file” on my calibration data and nothing seemed amiss.

    #144058

    MW
    Participant
    • Offline

    DisplayCAL turned off black point correction automatically because I have an LCD with a good contrast ratio

    Are you sure, I thought the logic is based on profile type selected.

    I know for a fact that there are no EDID primaries in play, because when I ordered my laptop’s LCD panel from the factory it had an incorrect EDID metadata block and I had to rewrite it from scratch.

    In that case you can temporarily write erroneous EDID defining the color space, to robustly verify that it’s not being used to affect output. For example swap the XY values between the red and green channel.

    #144059

    infradragon
    Participant
    • Offline

    The default of DisplayCAL is to turn off black point correction, and if you check the “auto” box it uses the ArgyllCMS logic which disables it if it thinks your display is a LCD. I know that black point correction was disabled because it says -k 0.0 was passed to dispcal in the logs.
    https://www.argyllcms.com/doc/dispcal.html#k

    Putting incorrect primaries in my EDID does work but I know that its not a factor because my normal setup has no primaries or other color information, and even if it did, KDE ignores them if you override them with an ICC profile.

    Attachments:
    You must be logged in to view attached files.
    #144061

    MW
    Participant
    • Offline

    The default of DisplayCAL is to turn off black point correction, and if you check the “auto” box it uses the ArgyllCMS logic which disables it if it thinks your display is a LCD. I know that black point correction was disabled because it says -k 0.0 was passed to dispcal in the logs.
    https://www.argyllcms.com/doc/dispcal.html#k

    Putting incorrect primaries in my EDID does work but I know that its not a factor because my normal setup has no primaries or other color information, and even if it did, KDE ignores them if you override them with an ICC profile.

    I see, I thought you were talking about the other BPC, black point compensation.

    BTW have you verified the VCGT can be used in your setup? If not it’s definitely advisable to use “tone curve:as measured”, otherwise the profile will blindly assume the VCGT is applied whether it is or not, leading to incorrect color. Skipping VCGT greys out black point correction, it’s not used outside of VCGT. This is a plus because you want to be mindful of the variables you are introducing at this stage. Given the issues you have encountered I would suggest profiling with a custom test chart consisting or pure RGB + 256 neutrals.

    First of all, you should really run a verification reports to verify the linear behavior of the display, only following that you can start being reasonable there is mainly a software issue. “Simulation profile” checked, simulation profile as display profile” checked, make sure no profile is loaded.

    • This reply was modified 10 months ago by MW.
    #144063

    infradragon
    Participant
    • Offline

    I’ll try making an ICC profile with the tone curve set to “as measured” overnight tonight and see if that fixes it.

    #144064

    DaniJ
    Participant
    • Offline

    The Monitor_1_1_2025-08-01_07-32_L_S_XYZLUTMTX.icc attached, contrary to what its name says, does not contain any XYZ 3DLUT.

    It only defines the primaries (very close to sRGB/Rec709) and 3 TRCs (no VCGT) which look very bad (see screenshot).

    The calibration curves in Monitor_1_1_2025-08-01_07-32_L_S_XYZLUTMTX.cal look decent (second screenshot) and can be used to make a VCGT.

    Curiosity: what lead you to measure 11107 points when the gamut is so close to the reference?

    Attachments:
    You must be logged in to view attached files.
Viewing 15 posts - 16 through 30 (of 64 total)

You must be logged in to reply to this topic.

Log in or Register

Display Calibration and Characterization powered by ArgyllCMS