Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion rawpy/_rawpy.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,9 @@ cdef class RawPy:
p.output_bps = params.output_bps
p.user_flip = params.user_flip
p.user_black = params.user_black
if params.user_cblack:
for i in range(4):
p.user_cblack[i] = params.user_cblack[i]
p.user_sat = params.user_sat
p.no_auto_bright = params.no_auto_bright
p.no_auto_scale = params.no_auto_scale
Expand Down Expand Up @@ -1101,7 +1104,7 @@ class Params(object):
noise_thr=None, median_filter_passes=0,
use_camera_wb=False, use_auto_wb=False, user_wb=None,
output_color=ColorSpace.sRGB, output_bps=8,
user_flip=None, user_black=None, user_sat=None,
user_flip=None, user_black=None, user_cblack=None, user_sat=None,
no_auto_bright=False, auto_bright_thr=None, adjust_maximum_thr=0.75,
bright=1.0, highlight_mode=HighlightMode.Clip,
exp_shift=None, exp_preserve_highlights=0.0, no_auto_scale=False,
Expand Down Expand Up @@ -1130,6 +1133,8 @@ class Params(object):
:param int user_flip: 0=none, 3=180, 5=90CCW, 6=90CW,
default is to use image orientation from the RAW image if available
:param int user_black: custom black level
:param list user_cblack: list of length 4 with per-channel corrections to user_black.
These are offsets applied on top of user_black for [R, G, B, G2] channels.
:param int user_sat: saturation adjustment (custom white level)
:param bool no_auto_scale: Whether to disable pixel value scaling
:param bool no_auto_bright: whether to disable automatic increase of brightness
Expand Down Expand Up @@ -1174,6 +1179,11 @@ class Params(object):
self.output_bps = output_bps
self.user_flip = user_flip if user_flip is not None else -1
self.user_black = user_black if user_black is not None else -1
if user_cblack is not None:
assert len(user_cblack) == 4
self.user_cblack = user_cblack
else:
self.user_cblack = [0, 0, 0, 0]
self.user_sat = user_sat if user_sat is not None else -1
self.no_auto_bright = no_auto_bright
self.no_auto_scale = no_auto_scale
Expand Down
76 changes: 76 additions & 0 deletions test/test_user_cblack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Tests for per-channel black level corrections (user_cblack parameter)."""
from __future__ import division, print_function, absolute_import

import os
import pytest
import numpy as np
from numpy.testing import assert_array_equal

import rawpy

thisDir = os.path.dirname(__file__)
rawTestPath = os.path.join(thisDir, 'iss030e122639.NEF')


def test_user_cblack_parameter_acceptance():
"""Test that the user_cblack parameter is accepted in Params constructor."""
# Test with valid 4-element list
params = rawpy.Params(user_cblack=[100, 200, 150, 200])
assert params.user_cblack == [100, 200, 150, 200]

# Test with None (default)
params = rawpy.Params()
assert params.user_cblack == [0, 0, 0, 0]


def test_user_cblack_parameter_validation():
"""Test that user_cblack parameter validates list length."""
# Should raise assertion error for wrong length
with pytest.raises(AssertionError):
rawpy.Params(user_cblack=[100, 200])

with pytest.raises(AssertionError):
rawpy.Params(user_cblack=[100, 200, 150])

with pytest.raises(AssertionError):
rawpy.Params(user_cblack=[100, 200, 150, 200, 250])


def test_user_cblack_postprocess():
"""Test that user_cblack can be used in postprocessing without errors."""
with rawpy.imread(rawTestPath) as raw:
# Process with per-channel black levels
rgb = raw.postprocess(user_cblack=[100, 100, 100, 100], no_auto_bright=True)
assert rgb.shape[2] == 3 # RGB image

# Process with different per-channel values
rgb2 = raw.postprocess(user_cblack=[50, 100, 150, 100], no_auto_bright=True)
assert rgb2.shape[2] == 3

# Images should be different when different black levels are applied
assert not np.array_equal(rgb, rgb2)


def test_user_cblack_vs_user_black():
"""Test that user_cblack and user_black can be used together in a single call.

user_cblack values are corrections/offsets applied on top of user_black.
For example: user_black=100, user_cblack=[10, 20, 30, 20] results in
effective black levels of [110, 120, 130, 120] for each channel.
"""
with rawpy.imread(rawTestPath) as raw:
# Process with both user_black and user_cblack in a single call
# user_cblack provides per-channel corrections on top of user_black base value
rgb = raw.postprocess(
user_black=100,
user_cblack=[10, 20, 30, 20],
no_auto_bright=True
)
assert rgb.shape[2] == 3 # RGB image

# Verify that using both parameters together works without errors
# and produces a valid image
assert rgb.dtype == np.uint8 # Default output_bps is 8

if __name__ == '__main__':
pytest.main([__file__, '-v'])