-module(viva_tensor@vision@transforms). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/viva_tensor/vision/transforms.gleam"). -export([resize/4, center_crop/3, random_crop/3, horizontal_flip/1, vertical_flip/1, random_horizontal_flip/2, normalize/3, to_grayscale/2, adjust_brightness/2, adjust_contrast/2, to_tensor/4, to_byte_image/1, compose/2]). -export_type([resize_mode/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -type resize_mode() :: nearest | bilinear. -file("src/viva_tensor/vision/transforms.gleam", 38). ?DOC(false). -spec chw_or_bchw(binary(), list(integer())) -> {ok, {integer(), integer(), integer(), integer(), boolean()}} | {error, viva_tensor@core@error:tensor_error()}. chw_or_bchw(Op, Shp) -> case Shp of [C, H, W] -> {ok, {1, C, H, W, false}}; [B, C@1, H@1, W@1] -> {ok, {B, C@1, H@1, W@1, true}}; _ -> {error, {rank_mismatch, Op, 3, Shp}} end. -file("src/viva_tensor/vision/transforms.gleam", 49). ?DOC(false). -spec make_shape(integer(), integer(), integer(), boolean(), integer()) -> list(integer()). make_shape(C, H, W, Batched, Batch) -> case Batched of true -> [Batch, C, H, W]; false -> [C, H, W] end. -file("src/viva_tensor/vision/transforms.gleam", 56). ?DOC(false). -spec min_float(float(), float()) -> float(). min_float(A, B) -> case A < B of true -> A; false -> B end. -file("src/viva_tensor/vision/transforms.gleam", 63). ?DOC(false). -spec max_float(float(), float()) -> float(). max_float(A, B) -> case A > B of true -> A; false -> B end. -file("src/viva_tensor/vision/transforms.gleam", 70). ?DOC(false). -spec clamp_f(float(), float(), float()) -> float(). clamp_f(X, Lo, Hi) -> max_float(Lo, min_float(Hi, X)). -file("src/viva_tensor/vision/transforms.gleam", 74). ?DOC(false). -spec clamp_i(integer(), integer(), integer()) -> integer(). clamp_i(X, Lo, Hi) -> case X < Lo of true -> Lo; false -> case X > Hi of true -> Hi; false -> X end end. -file("src/viva_tensor/vision/transforms.gleam", 85). ?DOC(false). -spec int_to_float(integer()) -> float(). int_to_float(I) -> erlang:float(I). -file("src/viva_tensor/vision/transforms.gleam", 89). ?DOC(false). -spec list_get(list(float()), integer()) -> float(). list_get(Xs, Idx) -> case Xs of [] -> +0.0; [Head | Rest] -> case Idx of 0 -> Head; _ -> list_get(Rest, Idx - 1) end end. -file("src/viva_tensor/vision/transforms.gleam", 104). ?DOC(false). -spec sample_clamped( list(float()), integer(), integer(), integer(), integer(), integer(), integer(), integer() ) -> float(). sample_clamped(Data, B, C, H, W, Channels, Height, Width) -> H_c = clamp_i(H, 0, Height - 1), W_c = clamp_i(W, 0, Width - 1), Idx = (((((B * Channels) * Height) * Width) + ((C * Height) * Width)) + (H_c * Width)) + W_c, list_get(Data, Idx). -file("src/viva_tensor/vision/transforms.gleam", 204). ?DOC(false). -spec sample_bilinear( list(float()), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer() ) -> float(). sample_bilinear(Data, B, Ch, Oh, Ow, Channels, In_h, In_w, Out_h, Out_w) -> Src_h = (case int_to_float(Out_h) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> (int_to_float(Oh) + 0.5) * int_to_float(In_h) / Gleam@denominator end) - 0.5, Src_w = (case int_to_float(Out_w) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> (int_to_float(Ow) + 0.5) * int_to_float(In_w) / Gleam@denominator@1 end) - 0.5, H_clamped = clamp_f(Src_h, +0.0, int_to_float(In_h - 1)), W_clamped = clamp_f(Src_w, +0.0, int_to_float(In_w - 1)), H0 = erlang:trunc(H_clamped), W0 = erlang:trunc(W_clamped), H1 = clamp_i(H0 + 1, 0, In_h - 1), W1 = clamp_i(W0 + 1, 0, In_w - 1), Dh = H_clamped - int_to_float(H0), Dw = W_clamped - int_to_float(W0), V00 = sample_clamped(Data, B, Ch, H0, W0, Channels, In_h, In_w), V01 = sample_clamped(Data, B, Ch, H0, W1, Channels, In_h, In_w), V10 = sample_clamped(Data, B, Ch, H1, W0, Channels, In_h, In_w), V11 = sample_clamped(Data, B, Ch, H1, W1, Channels, In_h, In_w), Top = (V00 * (1.0 - Dw)) + (V01 * Dw), Bot = (V10 * (1.0 - Dw)) + (V11 * Dw), (Top * (1.0 - Dh)) + (Bot * Dh). -file("src/viva_tensor/vision/transforms.gleam", 176). ?DOC(false). -spec sample_nearest( list(float()), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer() ) -> float(). sample_nearest(Data, B, Ch, Oh, Ow, Channels, In_h, In_w, Out_h, Out_w) -> Src_h = (case int_to_float(Out_h) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> (int_to_float(Oh) + 0.5) * int_to_float(In_h) / Gleam@denominator end) - 0.5, Src_w = (case int_to_float(Out_w) of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> (int_to_float(Ow) + 0.5) * int_to_float(In_w) / Gleam@denominator@1 end) - 0.5, Ih = erlang:round(Src_h), Iw = erlang:round(Src_w), sample_clamped(Data, B, Ch, Ih, Iw, Channels, In_h, In_w). -file("src/viva_tensor/vision/transforms.gleam", 736). ?DOC(false). -spec range_loop(integer(), integer(), list(integer())) -> list(integer()). range_loop(From, To, Acc) -> case From > To of true -> lists:reverse(Acc); false -> range_loop(From + 1, To, [From | Acc]) end. -file("src/viva_tensor/vision/transforms.gleam", 732). ?DOC(false). -spec range_int(integer(), integer()) -> list(integer()). range_int(From, To) -> range_loop(From, To, []). -file("src/viva_tensor/vision/transforms.gleam", 129). ?DOC(false). -spec resize(viva_tensor@tensor:tensor(), integer(), integer(), resize_mode()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. resize(Image, New_h, New_w, Mode) -> gleam@result:'try'( chw_or_bchw(<<"resize"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, case (New_h =< 0) orelse (New_w =< 0) of true -> {error, {invalid_shape, <<<<<<"resize: new_h and new_w must be positive, got "/utf8, (erlang:integer_to_binary(New_h))/binary>>/binary, "x"/utf8>>/binary, (erlang:integer_to_binary(New_w))/binary>>}}; false -> Data = viva_tensor@tensor:to_list(Image), Out_data = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map( _pipe, fun(B) -> _pipe@1 = range_int(0, C - 1), gleam@list:flat_map( _pipe@1, fun(Ch) -> _pipe@2 = range_int(0, New_h - 1), gleam@list:flat_map( _pipe@2, fun(Oh) -> _pipe@3 = range_int( 0, New_w - 1 ), gleam@list:map( _pipe@3, fun(Ow) -> case Mode of nearest -> sample_nearest( Data, B, Ch, Oh, Ow, C, H, W, New_h, New_w ); bilinear -> sample_bilinear( Data, B, Ch, Oh, Ow, C, H, W, New_h, New_w ) end end ) end ) end ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out_data), make_shape(C, New_h, New_w, Batched, Batch) ) end end ). -file("src/viva_tensor/vision/transforms.gleam", 304). ?DOC(false). -spec crop_region( viva_tensor@tensor:tensor(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), integer(), boolean() ) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. crop_region(Image, Batch, C, H, W, Top, Left, Target_h, Target_w, Batched) -> Data = viva_tensor@tensor:to_list(Image), Out = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map(_pipe, fun(B) -> _pipe@1 = range_int(0, C - 1), gleam@list:flat_map( _pipe@1, fun(Ch) -> _pipe@2 = range_int(0, Target_h - 1), gleam@list:flat_map( _pipe@2, fun(Oh) -> _pipe@3 = range_int(0, Target_w - 1), gleam@list:map( _pipe@3, fun(Ow) -> sample_clamped( Data, B, Ch, Top + Oh, Left + Ow, C, H, W ) end ) end ) end ) end) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), make_shape(C, Target_h, Target_w, Batched, Batch) ). -file("src/viva_tensor/vision/transforms.gleam", 252). ?DOC(false). -spec center_crop(viva_tensor@tensor:tensor(), integer(), integer()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. center_crop(Image, Target_h, Target_w) -> gleam@result:'try'( chw_or_bchw(<<"center_crop"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, case (((Target_h > H) orelse (Target_w > W)) orelse (Target_h =< 0)) orelse (Target_w =< 0) of true -> {error, {invalid_shape, <<"center_crop: target size exceeds image"/utf8>>}}; false -> Top = (H - Target_h) div 2, Left = (W - Target_w) div 2, crop_region( Image, Batch, C, H, W, Top, Left, Target_h, Target_w, Batched ) end end ). -file("src/viva_tensor/vision/transforms.gleam", 277). ?DOC(false). -spec random_crop(viva_tensor@tensor:tensor(), integer(), integer()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. random_crop(Image, Target_h, Target_w) -> gleam@result:'try'( chw_or_bchw(<<"random_crop"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, case (((Target_h > H) orelse (Target_w > W)) orelse (Target_h =< 0)) orelse (Target_w =< 0) of true -> {error, {invalid_shape, <<"random_crop: target size exceeds image"/utf8>>}}; false -> Max_top = H - Target_h, Max_left = W - Target_w, Top = case Max_top of 0 -> 0; N -> gleam@int:random(N + 1) end, Left = case Max_left of 0 -> 0; N@1 -> gleam@int:random(N@1 + 1) end, crop_region( Image, Batch, C, H, W, Top, Left, Target_h, Target_w, Batched ) end end ). -file("src/viva_tensor/vision/transforms.gleam", 342). ?DOC(false). -spec horizontal_flip(viva_tensor@tensor:tensor()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. horizontal_flip(Image) -> gleam@result:'try'( chw_or_bchw(<<"horizontal_flip"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, Data = viva_tensor@tensor:to_list(Image), Out = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map( _pipe, fun(B) -> _pipe@1 = range_int(0, C - 1), gleam@list:flat_map( _pipe@1, fun(Ch) -> _pipe@2 = range_int(0, H - 1), gleam@list:flat_map( _pipe@2, fun(Row) -> _pipe@3 = range_int(0, W - 1), gleam@list:map( _pipe@3, fun(Col) -> sample_clamped( Data, B, Ch, Row, (W - 1) - Col, C, H, W ) end ) end ) end ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), make_shape(C, H, W, Batched, Batch) ) end ). -file("src/viva_tensor/vision/transforms.gleam", 370). ?DOC(false). -spec vertical_flip(viva_tensor@tensor:tensor()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. vertical_flip(Image) -> gleam@result:'try'( chw_or_bchw(<<"vertical_flip"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, Data = viva_tensor@tensor:to_list(Image), Out = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map( _pipe, fun(B) -> _pipe@1 = range_int(0, C - 1), gleam@list:flat_map( _pipe@1, fun(Ch) -> _pipe@2 = range_int(0, H - 1), gleam@list:flat_map( _pipe@2, fun(Row) -> _pipe@3 = range_int(0, W - 1), gleam@list:map( _pipe@3, fun(Col) -> sample_clamped( Data, B, Ch, (H - 1) - Row, Col, C, H, W ) end ) end ) end ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), make_shape(C, H, W, Batched, Batch) ) end ). -file("src/viva_tensor/vision/transforms.gleam", 398). ?DOC(false). -spec random_horizontal_flip(viva_tensor@tensor:tensor(), float()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. random_horizontal_flip(Image, P) -> P_clamped = clamp_f(P, +0.0, 1.0), Resolution = 1000000, Threshold = erlang:round(P_clamped * int_to_float(Resolution)), case gleam@int:random(Resolution) < Threshold of true -> horizontal_flip(Image); false -> gleam@result:'try'( chw_or_bchw( <<"random_horizontal_flip"/utf8>>, viva_tensor@tensor:shape(Image) ), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list( viva_tensor@tensor:to_list(Image) ), make_shape(C, H, W, Batched, Batch) ) end ) end. -file("src/viva_tensor/vision/transforms.gleam", 430). ?DOC(false). -spec normalize(viva_tensor@tensor:tensor(), list(float()), list(float())) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. normalize(Image, Mean, Std) -> gleam@result:'try'( chw_or_bchw(<<"normalize"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, case (erlang:length(Mean) =:= C) andalso (erlang:length(Std) =:= C) of false -> {error, {invalid_shape, <<<<"normalize: mean/std length must equal channels ("/utf8, (erlang:integer_to_binary(C))/binary>>/binary, ")"/utf8>>}}; true -> case gleam@list:any(Std, fun(S) -> S =:= +0.0 end) of true -> {error, {invalid_shape, <<"normalize: std entries must be non-zero"/utf8>>}}; false -> Data = viva_tensor@tensor:to_list(Image), Out = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map( _pipe, fun(B) -> _pipe@1 = range_int(0, C - 1), gleam@list:flat_map( _pipe@1, fun(Ch) -> M = list_get(Mean, Ch), S@1 = list_get(Std, Ch), _pipe@2 = range_int(0, H - 1), gleam@list:flat_map( _pipe@2, fun(Row) -> _pipe@3 = range_int( 0, W - 1 ), gleam@list:map( _pipe@3, fun(Col) -> V = sample_clamped( Data, B, Ch, Row, Col, C, H, W ), case S@1 of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> (V - M) / Gleam@denominator end end ) end ) end ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), make_shape(C, H, W, Batched, Batch) ) end end end ). -file("src/viva_tensor/vision/transforms.gleam", 554). ?DOC(false). -spec slice_list(list(float()), integer(), integer()) -> list(float()). slice_list(Xs, Offset, Length) -> _pipe = Xs, _pipe@1 = gleam@list:drop(_pipe, Offset), gleam@list:take(_pipe@1, Length). -file("src/viva_tensor/vision/transforms.gleam", 490). ?DOC(false). -spec to_grayscale(viva_tensor@tensor:tensor(), integer()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. to_grayscale(Image, Num_output_channels) -> gleam@result:'try'( chw_or_bchw(<<"to_grayscale"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, case C =:= 3 of false -> {error, {invalid_shape, <<"to_grayscale: input must have 3 channels, got "/utf8, (erlang:integer_to_binary(C))/binary>>}}; true -> case (Num_output_channels =:= 1) orelse (Num_output_channels =:= 3) of false -> {error, {invalid_shape, <<"to_grayscale: num_output_channels must be 1 or 3, got "/utf8, (erlang:integer_to_binary( Num_output_channels ))/binary>>}}; true -> Data = viva_tensor@tensor:to_list(Image), Luma_data = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map( _pipe, fun(B) -> _pipe@1 = range_int(0, H - 1), gleam@list:flat_map( _pipe@1, fun(Row) -> _pipe@2 = range_int(0, W - 1), gleam@list:map( _pipe@2, fun(Col) -> R = sample_clamped( Data, B, 0, Row, Col, 3, H, W ), G = sample_clamped( Data, B, 1, Row, Col, 3, H, W ), Bl = sample_clamped( Data, B, 2, Row, Col, 3, H, W ), ((0.299 * R) + (0.587 * G)) + (0.114 * Bl) end ) end ) end ) end, case Num_output_channels of 1 -> viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Luma_data), make_shape(1, H, W, Batched, Batch) ); _ -> Plane_size = H * W, Out = begin _pipe@3 = range_int(0, Batch - 1), gleam@list:flat_map( _pipe@3, fun(B@1) -> Plane = slice_list( Luma_data, B@1 * Plane_size, Plane_size ), lists:append( [Plane, Plane, Plane] ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), make_shape(3, H, W, Batched, Batch) ) end end end end ). -file("src/viva_tensor/vision/transforms.gleam", 565). ?DOC(false). -spec adjust_brightness(viva_tensor@tensor:tensor(), float()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. adjust_brightness(Image, Factor) -> gleam@result:'try'( chw_or_bchw( <<"adjust_brightness"/utf8>>, viva_tensor@tensor:shape(Image) ), fun(_use0) -> {_, _, _, _, _} = _use0, Data = viva_tensor@tensor:to_list(Image), Out = gleam@list:map( Data, fun(V) -> clamp_f(V * Factor, +0.0, 1.0) end ), viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), viva_tensor@tensor:shape(Image) ) end ). -file("src/viva_tensor/vision/transforms.gleam", 585). ?DOC(false). -spec adjust_contrast(viva_tensor@tensor:tensor(), float()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. adjust_contrast(Image, Factor) -> gleam@result:'try'( chw_or_bchw(<<"adjust_contrast"/utf8>>, viva_tensor@tensor:shape(Image)), fun(_use0) -> {Batch, C, H, W, Batched} = _use0, Data = viva_tensor@tensor:to_list(Image), N = H * W, N_f = int_to_float(N), Out = begin _pipe = range_int(0, Batch - 1), gleam@list:flat_map( _pipe, fun(B) -> _pipe@1 = range_int(0, C - 1), gleam@list:flat_map( _pipe@1, fun(Ch) -> Plane = begin _pipe@2 = range_int(0, H - 1), gleam@list:flat_map( _pipe@2, fun(Row) -> _pipe@3 = range_int(0, W - 1), gleam@list:map( _pipe@3, fun(Col) -> sample_clamped( Data, B, Ch, Row, Col, C, H, W ) end ) end ) end, Sum = gleam@list:fold( Plane, +0.0, fun(Acc, V) -> Acc + V end ), Mean = case N_f of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> Sum / Gleam@denominator end, gleam@list:map( Plane, fun(V@1) -> clamp_f( Mean + (Factor * (V@1 - Mean)), +0.0, 1.0 ) end ) end ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Out), make_shape(C, H, W, Batched, Batch) ) end ). -file("src/viva_tensor/vision/transforms.gleam", 630). ?DOC(false). -spec to_tensor(list(integer()), integer(), integer(), integer()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. to_tensor(Byte_image, Height, Width, Channels) -> case ((Height =< 0) orelse (Width =< 0)) orelse (Channels =< 0) of true -> {error, {invalid_shape, <<<<<<<<<<<<"to_tensor: dimensions must be positive (got h="/utf8, (erlang:integer_to_binary(Height))/binary>>/binary, ", w="/utf8>>/binary, (erlang:integer_to_binary(Width))/binary>>/binary, ", c="/utf8>>/binary, (erlang:integer_to_binary(Channels))/binary>>/binary, ")"/utf8>>}}; false -> Expected = (Height * Width) * Channels, case erlang:length(Byte_image) =:= Expected of false -> {error, {invalid_shape, <<<<<<"to_tensor: byte_image length "/utf8, (erlang:integer_to_binary( erlang:length(Byte_image) ))/binary>>/binary, " != h*w*c = "/utf8>>/binary, (erlang:integer_to_binary(Expected))/binary>>}}; true -> Hwc = gleam@list:map(Byte_image, fun erlang:float/1), Chw = begin _pipe = range_int(0, Channels - 1), gleam@list:flat_map( _pipe, fun(Ch) -> _pipe@1 = range_int(0, Height - 1), gleam@list:flat_map( _pipe@1, fun(Row) -> _pipe@2 = range_int(0, Width - 1), gleam@list:map( _pipe@2, fun(Col) -> Idx = (((Row * Width) + Col) * Channels) + Ch, list_get(Hwc, Idx) / 255.0 end ) end ) end ) end, viva_tensor@tensor:reshape( viva_tensor@tensor:from_list(Chw), [Channels, Height, Width] ) end end. -file("src/viva_tensor/vision/transforms.gleam", 689). ?DOC(false). -spec to_byte_image(viva_tensor@tensor:tensor()) -> {ok, list(integer())} | {error, viva_tensor@core@error:tensor_error()}. to_byte_image(Image) -> Shp = viva_tensor@tensor:shape(Image), case Shp of [C, H, W] -> Data = viva_tensor@tensor:to_list(Image), Bytes = begin _pipe = range_int(0, H - 1), gleam@list:flat_map( _pipe, fun(Row) -> _pipe@1 = range_int(0, W - 1), gleam@list:flat_map( _pipe@1, fun(Col) -> _pipe@2 = range_int(0, C - 1), gleam@list:map( _pipe@2, fun(Ch) -> Idx = (((Ch * H) * W) + (Row * W)) + Col, Scaled = list_get(Data, Idx) * 255.0, Rounded = erlang:round(Scaled), clamp_i(Rounded, 0, 255) end ) end ) end ) end, {ok, Bytes}; _ -> {error, {rank_mismatch, <<"to_byte_image"/utf8>>, 3, Shp}} end. -file("src/viva_tensor/vision/transforms.gleam", 725). ?DOC(false). -spec compose( list(fun((viva_tensor@tensor:tensor()) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()})), viva_tensor@tensor:tensor() ) -> {ok, viva_tensor@tensor:tensor()} | {error, viva_tensor@core@error:tensor_error()}. compose(Transforms, Image) -> gleam@list:try_fold(Transforms, Image, fun(Acc, F) -> F(Acc) end).