yta-editor-nodes-gpu 0.0.1__tar.gz → 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yta-editor-nodes-gpu
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Youtube Autonomous Main Editor Nodes GPU module
5
5
  License-File: LICENSE
6
6
  Author: danialcala94
@@ -8,6 +8,10 @@ Author-email: danielalcalavalera@gmail.com
8
8
  Requires-Python: ==3.9
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.9
11
+ Requires-Dist: moderngl (>=0.0.1,<9.0.0)
12
+ Requires-Dist: yta_programming (>=0.0.1,<1.0.0)
13
+ Requires-Dist: yta_validation (>=0.0.1,<1.0.0)
14
+ Requires-Dist: yta_video_opengl (>=0.0.1,<1.0.0)
11
15
  Description-Content-Type: text/markdown
12
16
 
13
17
  # Youtube Autonomous Main Editor Nodes GPU module
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yta-editor-nodes-gpu"
3
- version = "0.0.1"
3
+ version = "0.0.2"
4
4
  description = "Youtube Autonomous Main Editor Nodes GPU module"
5
5
  authors = [
6
6
  {name = "danialcala94",email = "danielalcalavalera@gmail.com"}
@@ -8,7 +8,10 @@ authors = [
8
8
  readme = "README.md"
9
9
  requires-python = "==3.9"
10
10
  dependencies = [
11
-
11
+ "yta_validation (>=0.0.1,<1.0.0)",
12
+ "yta_programming (>=0.0.1,<1.0.0)",
13
+ "yta_video_opengl (>=0.0.1,<1.0.0)",
14
+ "moderngl (>=0.0.1,<9.0.0)",
12
15
  ]
13
16
 
14
17
  [tool.poetry]
@@ -17,6 +20,9 @@ packages = [{include = "yta_editor_nodes_gpu", from = "src"}]
17
20
  [tool.poetry.group.dev.dependencies]
18
21
  pytest = "^8.3.5"
19
22
  yta_testing = ">=0.0.1"
23
+ yta_editor_utils = ">=0.0.1"
24
+ pillow = ">=0.0.1"
25
+ # opencv-python = ">=0.0.1"
20
26
 
21
27
  [build-system]
22
28
  requires = ["poetry-core>=2.0.0,<3.0.0"]
@@ -0,0 +1,162 @@
1
+ """
2
+ The nodes that modify inputs by using static parameters
3
+ and not 't' time moments, with GPU.
4
+ """
5
+ from yta_video_opengl.abstract import _OpenGLBase
6
+ from yta_validation.parameter import ParameterValidator
7
+ from typing import Union
8
+
9
+ import moderngl
10
+
11
+
12
+ class _NodeProcessorGPU(_OpenGLBase):
13
+ """
14
+ *Singleton class*
15
+
16
+ *For internal use only*
17
+
18
+ Class to represent a node processor that uses GPU
19
+ to transform the input.
20
+
21
+ The basic class of a node to manipulate frames as
22
+ opengl textures. This node will process the frame
23
+ as an input texture and will generate also a
24
+ texture as the output.
25
+
26
+ Nodes can be chained and the result from one node
27
+ can be applied on another node.
28
+ """
29
+
30
+ # TODO: Do we really need this class just to add
31
+ # one step on the hierarchy? It is to differentiate
32
+ # between any class that uses OpenGL on the base
33
+ # from the ones that are nodes to process and to
34
+ # be added to a general NodeProcessor class
35
+
36
+ def __init__(
37
+ self,
38
+ opengl_context: Union[moderngl.Context, None],
39
+ output_size: tuple[int, int],
40
+ **kwargs
41
+ ):
42
+ """
43
+ Provide all the variables you want to be initialized
44
+ as uniforms at the begining for the global OpenGL
45
+ animation in the `**kwargs`.
46
+
47
+ The `output_size` is the size (width, height) of the
48
+ texture that will be obtained as result. This size
49
+ can be modified when processing a specific input, but
50
+ be consider the cost of resources of modifying the
51
+ size, that will regenerate the output texture.
52
+ """
53
+ super().__init__(
54
+ opengl_context = opengl_context,
55
+ output_size = output_size,
56
+ **kwargs
57
+ )
58
+
59
+ def __reinit__(
60
+ self,
61
+ opengl_context: Union[moderngl.Context, None] = None,
62
+ output_size: Union[tuple[int, int], None] = None,
63
+ **kwargs
64
+ ):
65
+ # TODO: We ignore the 'opengl_context' as we don't need
66
+ # to reset it
67
+ super().__reinit__(
68
+ output_size = output_size,
69
+ **kwargs
70
+ )
71
+
72
+ class SelectionMaskProcessorGPU(_NodeProcessorGPU):
73
+ """
74
+ Class to use a mask selection (from which we will
75
+ read the red color to determine if the pixel must
76
+ be applied or not) to apply the `processed_texture`
77
+ on the `original_texture`.
78
+
79
+ If the selection mask is completely full, the
80
+ result will be the `processed_texture`.
81
+ """
82
+
83
+ @property
84
+ def fragment_shader(
85
+ self
86
+ ):
87
+ return (
88
+ '''
89
+ #version 330
90
+
91
+ uniform sampler2D original_texture;
92
+ uniform sampler2D processed_texture;
93
+ // White = apply, black = ignore
94
+ uniform sampler2D selection_mask_texture;
95
+ in vec2 v_uv;
96
+ out vec4 output_color;
97
+
98
+ void main() {
99
+ vec4 original_color = texture(original_texture, v_uv);
100
+ vec4 processed_color = texture(processed_texture, v_uv);
101
+ // We use the red as the value
102
+ float mask = texture(selection_mask_texture, v_uv).r;
103
+
104
+ output_color = mix(original_color, processed_color, mask);
105
+ }
106
+ '''
107
+ )
108
+
109
+ def _prepare_input_textures(
110
+ self
111
+ ) -> '_OpenGLBase':
112
+ """
113
+ *For internal use only*
114
+
115
+ Set the input texture variables and handlers
116
+ we need to manage this. This method has to be
117
+ called only once, just to set the slot for
118
+ the different textures we will use (and are
119
+ registered as textures in the shader).
120
+ """
121
+ self.textures.add('original_texture', 0)
122
+ self.textures.add('processed_texture', 1)
123
+ self.textures.add('selection_mask_texture', 2)
124
+
125
+ return self
126
+
127
+ def process(
128
+ self,
129
+ original_input: Union[moderngl.Texture, 'np.ndarray'],
130
+ processed_input: Union[moderngl.Texture, 'np.ndarray'],
131
+ selection_mask_input: Union[moderngl.Texture, 'np.ndarray'],
132
+ output_size: Union[tuple[int, int], None] = None,
133
+ **kwargs
134
+ ) -> moderngl.Texture:
135
+ """
136
+ Mix the `processed_input` with the
137
+ `original_input` as the `selecction_mask_input`
138
+ says.
139
+
140
+ You can provide any additional parameter
141
+ in the **kwargs, but be careful because
142
+ this could overwrite other uniforms that
143
+ were previously set.
144
+
145
+ We use and return textures to maintain
146
+ the process in GPU and optimize it.
147
+ """
148
+ ParameterValidator.validate_mandatory_instance_of('original_input', original_input, [moderngl.Texture, 'np.ndarray'])
149
+ ParameterValidator.validate_mandatory_instance_of('processed_input', processed_input, [moderngl.Texture, 'np.ndarray'])
150
+ ParameterValidator.validate_mandatory_instance_of('selection_mask_input', selection_mask_input, [moderngl.Texture, 'np.ndarray'])
151
+
152
+ textures_map = {
153
+ 'original_texture': original_input,
154
+ 'processed_texture': processed_input,
155
+ 'selection_mask_texture': selection_mask_input
156
+ }
157
+
158
+ return self._process_common(
159
+ textures_map = textures_map,
160
+ output_size = output_size,
161
+ **kwargs
162
+ )
@@ -0,0 +1,447 @@
1
+ """
2
+ This entire module is working with OpenGL.
3
+
4
+ We don't name it 'blend' instead of 'process' because
5
+ the GPU classes have the 'process' name due to the
6
+ inheritance from OpenGL classes, and we want to avoid
7
+ misunderstandings.
8
+
9
+ (!) IMPORTANT:
10
+ In all our blenders we need to include a platform
11
+ called `mix_weight` to determine the percentage of
12
+ effect we want to apply to the output result:
13
+ - `uniform float mix_weight; // 0.0 → only base, 1.0 → only overlay`
14
+ - `output_color = mix(base_color, overlay_color, mix_weight);`
15
+ """
16
+ from yta_video_opengl.abstract import _OpenGLBase
17
+ from yta_editor_nodes_gpu.processor.blender.utils import _validate_inputs_and_mix_weights
18
+ from yta_video_opengl.utils import frame_to_texture
19
+ from yta_validation.parameter import ParameterValidator
20
+ from yta_validation import PythonValidator
21
+ from typing import Union
22
+
23
+ import numpy as np
24
+ import moderngl
25
+
26
+
27
+ # TODO: I think this one should inherit from
28
+ # '_NodeProcessorGPU'
29
+ class _BlenderGPU(_OpenGLBase):
30
+ """
31
+ *Singleton class*
32
+
33
+ *For internal use only*
34
+
35
+ *This class has to be inherited*
36
+
37
+ Class to represent a blender that uses GPU to
38
+ blend the inputs. Base class for an OpenGL
39
+ blender, that will be inherited by the specific
40
+ implementations.
41
+
42
+ The basic class of a blender to blend frames as
43
+ opengl textures. This blender will process the
44
+ inputs as textures and will generate also a
45
+ texture as the output.
46
+
47
+ Blender results can be chained and the result
48
+ from one node can be applied on another node.
49
+
50
+ This blender is able to work acting as an
51
+ opacity blender, but will be used as the base
52
+ of the classes that we will implement actually.
53
+
54
+ The `opacity` value must be set as an uniform
55
+ each time we are processing the 2 textures.
56
+ """
57
+
58
+ @property
59
+ def fragment_shader(
60
+ self
61
+ ) -> str:
62
+ """
63
+ The code of the fragment shader.
64
+ """
65
+ return (
66
+ '''
67
+ #version 330
68
+ uniform sampler2D base_texture;
69
+ uniform sampler2D overlay_texture;
70
+ uniform float mix_weight; // 0.0 → only base, 1.0 → only overlay
71
+ in vec2 v_uv;
72
+ out vec4 output_color;
73
+
74
+ void main() {
75
+ vec4 base_color = texture(base_texture, v_uv);
76
+ vec4 overlay_color = texture(overlay_texture, v_uv);
77
+
78
+ // Apply global mix intensity
79
+ output_color = mix(base_color, overlay_color, mix_weight);
80
+ }
81
+ '''
82
+ )
83
+
84
+ def __init__(
85
+ self,
86
+ opengl_context: Union[moderngl.Context, None],
87
+ output_size: tuple[int, int],
88
+ mix_weight: float = 1.0,
89
+ **kwargs
90
+ ):
91
+ ParameterValidator.validate_mandatory_number_between('mix_weight', mix_weight, 0.0, 1.0)
92
+
93
+ super().__init__(
94
+ opengl_context = opengl_context,
95
+ output_size = output_size,
96
+ mix_weight = mix_weight,
97
+ **kwargs
98
+ )
99
+
100
+ # TODO: Overwrite this method
101
+ def _prepare_input_textures(
102
+ self
103
+ ) -> '_BlenderGPU':
104
+ """
105
+ *For internal use only*
106
+
107
+ Set the input texture variables and handlers
108
+ we need to manage this. This method has to be
109
+ called only once, just to set the slot for
110
+ the different textures we will use (and are
111
+ registered as textures in the shader).
112
+ """
113
+ self.textures.add('base_texture', 0)
114
+ self.textures.add('overlay_texture', 1)
115
+
116
+ return self
117
+
118
+ # TODO: Overwrite this method
119
+ def process(
120
+ self,
121
+ base_input: Union[moderngl.Texture, np.ndarray],
122
+ overlay_input: Union[moderngl.Texture, np.ndarray],
123
+ output_size: Union[tuple[int, int], None] = None,
124
+ mix_weight: float = 1.0,
125
+ **kwargs
126
+ ) -> moderngl.Texture:
127
+ """
128
+ Validate the parameters, set the textures map, process
129
+ the mix by applying the blender logic, and return the
130
+ result but only applied as much as the `mix_weight`
131
+ parameter is indicating.
132
+
133
+ Apply the shader to the given 'base_input'
134
+ and 'overlay_input', that must be frames or
135
+ textures, and return the new resulting
136
+ texture.
137
+
138
+ You can provide any additional parameter
139
+ in the **kwargs, but be careful because
140
+ this could overwrite other uniforms that
141
+ were previously set.
142
+
143
+ We use and return textures to maintain
144
+ the process in GPU and optimize it.
145
+ """
146
+ ParameterValidator.validate_mandatory_instance_of('base_input', base_input, [moderngl.Texture, np.ndarray])
147
+ ParameterValidator.validate_mandatory_instance_of('overlay_input', overlay_input, [moderngl.Texture, np.ndarray])
148
+ ParameterValidator.validate_mandatory_number_between('mix_weight', mix_weight, 0.0, 1.0)
149
+
150
+ if mix_weight == 0.0:
151
+ # If the mix_weight is 0.0 we don't want to affect
152
+ # the base so we don't even need to process the
153
+ # mix but return the original base input as a
154
+ # Texture
155
+ return (
156
+ base_input
157
+ if PythonValidator.is_instance_of(base_input, moderngl.Texture) else
158
+ frame_to_texture(base_input, self.context)
159
+ )
160
+
161
+ textures_map = {
162
+ 'base_texture': base_input,
163
+ 'overlay_texture': overlay_input
164
+ }
165
+
166
+ return self._process_common(
167
+ textures_map = textures_map,
168
+ output_size = output_size,
169
+ mix_weight = mix_weight,
170
+ **kwargs
171
+ )
172
+
173
+ # TODO: This method can change according to the way
174
+ # we need to process the inputs, that could be
175
+ # different than by pairs
176
+ def process_multiple_inputs(
177
+ self,
178
+ inputs: list[Union[np.ndarray, moderngl.Texture]],
179
+ mix_weights: Union[list[float], float] = 1.0,
180
+ output_size: Union[tuple[int, int], None] = None,
181
+ ) -> moderngl.Texture:
182
+ """
183
+ Blend all the `inputs` provided, one after another,
184
+ applying the `mix_weights` provided, and forcing the
185
+ result to the `dtype` if provided.
186
+
187
+ The `mix_weights` can be a single float value, that
188
+ will be used for all the mixings, or a list of as
189
+ many float values as `inputs` received, to be
190
+ applied individually to each mixing.
191
+ """
192
+ _validate_inputs_and_mix_weights(
193
+ inputs = inputs,
194
+ mix_weights = mix_weights
195
+ )
196
+
197
+ # We process all the 'inputs' as 'base' and 'overlay'
198
+ # and accumulate the result
199
+
200
+ # Use the first one as the base
201
+ output = inputs[0]
202
+
203
+ # TODO: How do we handle the additional parameters that
204
+ # could be an array? Maybe if it is an array, check that
205
+ # the number of elements is the same as the number of
206
+ # inputs, and if a single value just use it...
207
+
208
+ for i in range(1, len(inputs)):
209
+ overlay_input = inputs[i]
210
+ mix_weight = mix_weights[i]
211
+
212
+ output = self.process(
213
+ base_input = output,
214
+ overlay_input = overlay_input,
215
+ output_size = output_size,
216
+ mix_weight = mix_weight,
217
+ )
218
+
219
+ return output
220
+
221
+ # Specific implementations below
222
+ class MixBlenderGPU(_BlenderGPU):
223
+ """
224
+ Blender to blend 2 textures by using a mix float
225
+ parameter that determines the amount of the result
226
+ we want to mix with the input.
227
+ """
228
+ pass
229
+
230
+ class AlphaBlenderGPU(_BlenderGPU):
231
+ """
232
+ Blender to blend 2 textures by using the most common
233
+ blending method, which is the alpha.
234
+
235
+ This blender will use the alpha channel of the
236
+ overlay input, multiplied by the `blend_strength`
237
+ parameter provided, to use it as the mixer factor
238
+ between the base and the overlay inputs.
239
+ """
240
+
241
+ @property
242
+ def fragment_shader(
243
+ self
244
+ ) -> str:
245
+ """
246
+ The code of the fragment shader.
247
+ """
248
+ # TODO: What if no 'overlay_color.a' (?)
249
+ return (
250
+ '''
251
+ #version 330
252
+ uniform sampler2D base_texture;
253
+ uniform sampler2D overlay_texture;
254
+ uniform float mix_weight; // 0.0 → only base, 1.0 → only overlay
255
+ uniform float blend_strength;
256
+ in vec2 v_uv;
257
+ out vec4 output_color;
258
+
259
+ void main() {
260
+ vec4 base_color = texture(base_texture, v_uv);
261
+ vec4 overlay_color = texture(overlay_texture, v_uv);
262
+
263
+ // Use alpha channel of overlay if available
264
+ float alpha = overlay_color.a * blend_strength;
265
+
266
+ // Classic blending
267
+ output_color = mix(base_color, overlay_color, alpha);
268
+
269
+ // Apply global mix intensity
270
+ output_color = mix(base_color, output_color, mix_weight);
271
+ }
272
+ '''
273
+ )
274
+
275
+ def __init__(
276
+ self,
277
+ opengl_context: Union[moderngl.Context, None],
278
+ output_size: tuple[int, int]
279
+ ):
280
+ super().__init__(
281
+ opengl_context = opengl_context,
282
+ output_size = output_size
283
+ )
284
+
285
+ def process(
286
+ self,
287
+ base_input: Union[moderngl.Texture, np.ndarray],
288
+ overlay_input: Union[moderngl.Texture, np.ndarray],
289
+ output_size: Union[tuple[int, int], None] = None,
290
+ mix_weight: float = 1.0,
291
+ blend_strength: float = 0.5
292
+ ) -> moderngl.Texture:
293
+ """
294
+ Validate the parameters, set the textures map, process
295
+ the mix by applying the blender logic, and return the
296
+ result but only applied as much as the `mix_weight`
297
+ parameter is indicating.
298
+
299
+ Apply the shader to the given 'base_input'
300
+ and 'overlay_input', that must be frames or
301
+ textures, and return the new resulting
302
+ texture.
303
+
304
+ You can provide any additional parameter
305
+ in the **kwargs, but be careful because
306
+ this could overwrite other uniforms that
307
+ were previously set.
308
+
309
+ We use and return textures to maintain
310
+ the process in GPU and optimize it.
311
+ """
312
+ ParameterValidator.validate_mandatory_instance_of('base_input', base_input, [moderngl.Texture, np.ndarray])
313
+ ParameterValidator.validate_mandatory_instance_of('overlay_input', overlay_input, [moderngl.Texture, np.ndarray])
314
+ ParameterValidator.validate_mandatory_number_between('mix_weight', mix_weight, 0.0, 1.0)
315
+ ParameterValidator.validate_mandatory_number_between('blend_strength', blend_strength, 0.0, 1.0)
316
+
317
+ if mix_weight == 0.0:
318
+ # If the mix_weight is 0.0 we don't want to affect
319
+ # the base so we don't even need to process the
320
+ # mix but return the original base input as a
321
+ # Texture
322
+ return (
323
+ base_input
324
+ if PythonValidator.is_instance_of(base_input, moderngl.Texture) else
325
+ frame_to_texture(base_input, self.context)
326
+ )
327
+
328
+ textures_map = {
329
+ 'base_texture': base_input,
330
+ 'overlay_texture': overlay_input
331
+ }
332
+
333
+ return self._process_common(
334
+ textures_map = textures_map,
335
+ output_size = output_size,
336
+ mix_weight = mix_weight,
337
+ blend_strength = blend_strength
338
+ )
339
+
340
+ class AddBlenderGPU(_BlenderGPU):
341
+ """
342
+ Blender to blend 2 textures by adding their values.
343
+
344
+ This blender will increase the brightness by
345
+ combining the colors of the base and the overlay
346
+ inputs, using the overlay as much as the
347
+ `stregth` parameter is indicating.
348
+ """
349
+
350
+ @property
351
+ def fragment_shader(
352
+ self
353
+ ) -> str:
354
+ """
355
+ The code of the fragment shader.
356
+ """
357
+ return (
358
+ '''
359
+ #version 330
360
+ uniform sampler2D base_texture;
361
+ uniform sampler2D overlay_texture;
362
+ uniform float mix_weight; // 0.0 → only base, 1.0 → only overlay
363
+ uniform float strength; // 1.0 = full additive, 0.5 = subtle
364
+ in vec2 v_uv;
365
+ out vec4 output_color;
366
+
367
+ void main() {
368
+ vec4 base_color = texture(base_texture, v_uv);
369
+ vec4 overlay_color = texture(overlay_texture, v_uv);
370
+
371
+ vec4 result = base_color + overlay_color * strength;
372
+
373
+ // Clamp to avoid overflow (values > 1.0)
374
+ output_color = clamp(result, 0.0, 1.0);
375
+
376
+ // Apply global mix intensity
377
+ output_color = mix(base_color, output_color, mix_weight);
378
+ }
379
+ '''
380
+ )
381
+
382
+ def __init__(
383
+ self,
384
+ opengl_context: Union[moderngl.Context, None],
385
+ output_size: tuple[int, int],
386
+ ):
387
+ super().__init__(
388
+ opengl_context = opengl_context,
389
+ output_size = output_size,
390
+ )
391
+
392
+ def process(
393
+ self,
394
+ base_input: Union[moderngl.Texture, np.ndarray],
395
+ overlay_input: Union[moderngl.Texture, np.ndarray],
396
+ output_size: Union[tuple[int, int], None] = None,
397
+ mix_weight: float = 1.0,
398
+ strength: float = 0.5
399
+ ) -> moderngl.Texture:
400
+ """
401
+ Validate the parameters, set the textures map, process
402
+ the mix by applying the blender logic, and return the
403
+ result but only applied as much as the `mix_weight`
404
+ parameter is indicating.
405
+
406
+ Apply the shader to the given 'base_input'
407
+ and 'overlay_input', that must be frames or
408
+ textures, and return the new resulting
409
+ texture.
410
+
411
+ You can provide any additional parameter
412
+ in the **kwargs, but be careful because
413
+ this could overwrite other uniforms that
414
+ were previously set.
415
+
416
+ We use and return textures to maintain
417
+ the process in GPU and optimize it.
418
+ """
419
+ ParameterValidator.validate_mandatory_instance_of('base_input', base_input, [moderngl.Texture, np.ndarray])
420
+ ParameterValidator.validate_mandatory_instance_of('overlay_input', overlay_input, [moderngl.Texture, np.ndarray])
421
+ ParameterValidator.validate_mandatory_number_between('mix_weight', mix_weight, 0.0, 1.0)
422
+ ParameterValidator.validate_mandatory_number_between('strength', strength, 0.0, 1.0)
423
+
424
+ if mix_weight == 0.0:
425
+ # If the mix_weight is 0.0 we don't want to affect
426
+ # the base so we don't even need to process the
427
+ # mix but return the original base input as a
428
+ # Texture
429
+ return (
430
+ base_input
431
+ if PythonValidator.is_instance_of(base_input, moderngl.Texture) else
432
+ frame_to_texture(base_input, self.context)
433
+ )
434
+
435
+ textures_map = {
436
+ 'base_texture': base_input,
437
+ 'overlay_texture': overlay_input
438
+ }
439
+
440
+ return self._process_common(
441
+ textures_map = textures_map,
442
+ output_size = output_size,
443
+ mix_weight = mix_weight,
444
+ strength = strength
445
+ )
446
+
447
+ # TODO: Create more
@@ -0,0 +1,44 @@
1
+ """
2
+ TODO: This module is duplicated as it is repeated
3
+ in the 'yta-editor-nodes-gpu' library. It should
4
+ be in a 'yta-editor-nodes-common' library.
5
+ """
6
+ from yta_validation.parameter import ParameterValidator
7
+ from yta_validation.number import NumberValidator
8
+ from yta_validation import PythonValidator
9
+ from typing import Union
10
+
11
+ import numpy as np
12
+
13
+
14
+ def _validate_inputs_and_mix_weights(
15
+ inputs: list[Union[np.ndarray, 'moderngl.Texture']],
16
+ mix_weights: Union[list[float], float] = 1.0,
17
+ ) -> None:
18
+ """
19
+ *For internal use only*
20
+
21
+ Validate the provided 'inputs' and 'mix_weights' raising
22
+ an exception if something wrong was found.
23
+ """
24
+ ParameterValidator.validate_mandatory_list_of_these_instances('inputs', inputs, [np.ndarray, 'moderngl.Texture'])
25
+
26
+ if len(inputs) < 2:
27
+ raise Exception('The amount of "inputs" provided is less than 2.')
28
+
29
+ if not all(input.size == inputs[0].size for input in inputs):
30
+ # TODO: Should we raise this exception (?)
31
+ raise Exception('All the inputs do not have the same size.')
32
+
33
+ # Validate 'mix_weights' as a single or list of floats
34
+ # in the [0.0, 1.0] range
35
+ if PythonValidator.is_list_of_float(mix_weights):
36
+ if len(mix_weights) != (len(inputs) - 1):
37
+ raise Exception('The number of "mix_weights" values provided is not the same as "inputs".')
38
+
39
+ for mix_weight in mix_weights:
40
+ ParameterValidator.validate_mandatory_number_between('mix_weight', mix_weight, 0.0, 1.0)
41
+ elif NumberValidator.is_number_between(mix_weights, 0.0, 1.0):
42
+ mix_weights = [mix_weights] * len(inputs)
43
+ else:
44
+ raise Exception('The "mix_weights" parameter provided is not valid.')