Hi,
I spent some more time digging tonight and turns out that a very close fitting function is as follows (180 just happened to be a convenient rounded value):

Plotted here:
Difference * 5
With vs Without
So this almost fixes the dreaded magenta cast plaguing the HSV model while giving some controls over the hue.
Parameterising the 180 constant allows to vary the clumping around the primary and secondary colours. I have not rolled that yet in the Jupyter Notebook but here are some Nuke nodes (that do not have the Hue Twist Controls):
set cut_paste_input [stack 0]
version 12.1 v1
Read {
inputs 0
file_type exr
file /Users/kelsolaar/Documents/Development/colour-science/gamut-mapping-ramblings/resources/images/A009C002_190210_R0EI_Alexa_LogCWideGamut.exr
format "1024 659 0 0 1024 659 1 "
origset true
name Read15
selected true
xpos -40
ypos -114
}
Shuffle2 {
fromInput1 {{0} B}
fromInput2 {{0} B}
mappings "4 rgba.red 0 0 rgba.red 0 0 rgba.green 0 1 rgba.green 0 1 rgba.blue 0 2 rgba.blue 0 2 white -1 -1 rgba.alpha 0 3"
name Shuffle1
selected true
xpos -40
ypos -33
}
ColorMatrix {
matrix {
{1.451439316 -0.2365107469 -0.2149285693}
{-0.0765537733 1.1762297 -0.0996759265}
{0.0083161484 -0.0060324498 0.9977163014}
}
name ACES2065-1_to_ACEScg
selected true
xpos -40
ypos -9
}
set N27d0e000 [stack 0]
Expression {
temp_name0 cmax
temp_expr0 max(r,g,b)
temp_name1 cmin
temp_expr1 min(r,g,b)
temp_name2 delta
temp_expr2 cmax-cmin
expr0 delta==0?0:cmax==r?(((g-b)/delta+6)%6)/6:cmax==g?(((b-r)/delta+2)/6):(((r-g)/delta+4)/6)
expr1 "cmax == 0 ? 0 : delta / cmax"
expr2 cmax
name RGB_to_HSV
note_font "Bitstream Vera Sans"
selected true
xpos -40
ypos 39
}
set N279a9800 [stack 0]
Expression {
temp_name0 i
temp_expr0 i_Floating_Point_Slider
temp_name1 o
temp_expr1 o_Floating_Point_Slider
expr0 "clamp((r - i) / (o - i) ** 2 * (3 - 2 * (r - i) / (o - i)))"
expr1 "clamp((g - i) / (o - i) ** 2 * (3 - 2 * (g - i) / (o - i)))"
expr2 "clamp((b - i) / (o - i) ** 2 * (3 - 2 * (b - i) / (o - i)))"
name Smoothstep
selected true
xpos -150
ypos 87
addUserKnob {20 User}
addUserKnob {7 i_Floating_Point_Slider l i}
i_Floating_Point_Slider {0.7}
addUserKnob {7 o_Floating_Point_Slider l o}
o_Floating_Point_Slider {{parent.tanh_Compression.t_Floating_Point_Slider}}
}
push $N27d0e000
push $N279a9800
Dot {
name Dot17
selected true
xpos 104
ypos 42
}
Expression {
temp_name0 t
temp_expr0 t_Floating_Point_Slider
temp_name1 l
temp_expr1 l_Floating_Point_Slider
expr0 "r > t ? t + l * tanh((r - t) / l) : r"
expr1 "g > t ? t + l * tanh((g - t) / l) : g"
expr2 "b > t ? t + l * tanh((b - t) / l) : b"
name tanh_Compression
selected true
xpos 70
ypos 87
addUserKnob {20 User}
addUserKnob {7 t_Floating_Point_Slider l a}
t_Floating_Point_Slider 0.8
addUserKnob {7 l_Floating_Point_Slider l b}
l_Floating_Point_Slider {{"1 - t_Floating_Point_Slider"}}
}
Dot {
name Dot18
selected true
xpos 104
ypos 162
}
push $N279a9800
Expression {
expr0 "r + sin(r*pi*6+pi)*(pi/a_Floating_Point_Slider)"
expr1 "g + sin(g*pi*6+pi)*(pi/a_Floating_Point_Slider)"
expr2 "b + sin(b*pi*6+pi)*(pi/a_Floating_Point_Slider)"
name Clumping_Expression
selected true
xpos -40
ypos 111
addUserKnob {20 User}
addUserKnob {7 a_Floating_Point_Slider l a R 90 270}
a_Floating_Point_Slider 180
}
Expression {
expr0 "r % 1"
name Modulus_Expression
selected true
xpos -40
ypos 135
}
ShuffleCopy {
inputs 2
green green
alpha alpha2
name ShuffleCopy
note_font "Bitstream Vera Sans"
selected true
xpos -40
ypos 159
}
Expression {
temp_name0 C
temp_expr0 b*g
temp_name1 X
temp_expr1 C*(1-abs((r*6)%2-1))
temp_name2 m
temp_expr2 b-C
expr0 (r<1/6?C:r<2/6?X:r<3/6?0:r<4/6?0:r<5/6?X:C)+m
expr1 (r<1/6?X:r<2/6?C:r<3/6?C:r<4/6?X:r<5/6?0:0)+m
expr2 (r<1/6?0:r<2/6?0:r<3/6?X:r<4/6?C:r<5/6?C:X)+m
name HSV_to_RGB
note_font "Bitstream Vera Sans"
selected true
xpos -40
ypos 183
}
Merge2 {
inputs 2+1
maskChannelMask rgba.red
name Merge1
selected true
xpos -150
ypos 183
}
Viewer {
frame_range 1-100
viewerProcess "sRGB (ACES)"
name Viewer1
selected true
xpos -150
ypos 207
}
Cheers,
Thomas


