Blender V5.0
node_geo_attribute_statistic.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <algorithm>
6#include <numeric>
7
8#include "NOD_rna_define.hh"
9
11#include "UI_resources.hh"
12
13#include "BLI_array_utils.hh"
14
16
17#include "RNA_enum_types.hh"
18
19#include "node_geometry_util.hh"
20
22
24{
25 const bNode *node = b.node_or_null();
26
27 b.add_input<decl::Geometry>("Geometry").description("Geometry to get the statistics from");
28 b.add_input<decl::Bool>("Selection").default_value(true).field_on_all().hide_value();
29
30 if (node != nullptr) {
31 const eCustomDataType data_type = eCustomDataType(node->custom1);
32 b.add_input(data_type, "Attribute").hide_value().field_on_all();
33
34 b.add_output(data_type, N_("Mean"));
35 b.add_output(data_type, CTX_N_(BLT_I18NCONTEXT_ID_NODETREE, "Median"))
36 .translation_context(BLT_I18NCONTEXT_ID_NODETREE);
37 b.add_output(data_type, N_("Sum"));
38 b.add_output(data_type, N_("Min"));
39 b.add_output(data_type, N_("Max"));
40 b.add_output(data_type, N_("Range"));
41 b.add_output(data_type, N_("Standard Deviation"));
42 b.add_output(data_type, N_("Variance"));
43 }
44}
45
46static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
47{
48 layout->prop(ptr, "data_type", UI_ITEM_NONE, "", ICON_NONE);
49 layout->prop(ptr, "domain", UI_ITEM_NONE, "", ICON_NONE);
50}
51
52static void node_init(bNodeTree * /*tree*/, bNode *node)
53{
54 node->custom1 = CD_PROP_FLOAT;
55 node->custom2 = int16_t(AttrDomain::Point);
56}
57
58static std::optional<eCustomDataType> node_type_from_other_socket(const bNodeSocket &socket)
59{
60 switch (socket.type) {
61 case SOCK_FLOAT:
62 case SOCK_BOOLEAN:
63 case SOCK_INT:
64 return CD_PROP_FLOAT;
65 case SOCK_VECTOR:
66 case SOCK_RGBA:
67 case SOCK_ROTATION:
68 return CD_PROP_FLOAT3;
69 default:
70 return {};
71 }
72}
73
75{
76 const blender::bke::bNodeType &node_type = params.node_type();
77 const NodeDeclaration &declaration = *params.node_type().static_declaration;
79
80 const std::optional<eCustomDataType> type = node_type_from_other_socket(params.other_socket());
81 if (!type) {
82 return;
83 }
84
85 if (params.in_out() == SOCK_IN) {
86 params.add_item(IFACE_("Attribute"), [node_type, type](LinkSearchOpParams &params) {
87 bNode &node = params.add_node(node_type);
88 node.custom1 = *type;
89 params.update_and_connect_available_socket(node, "Attribute");
90 });
91 }
92 else {
93 for (const StringRefNull name :
94 {"Mean", "Median", "Sum", "Min", "Max", "Range", "Standard Deviation", "Variance"})
95 {
96 params.add_item(IFACE_(name), [node_type, name, type](LinkSearchOpParams &params) {
97 bNode &node = params.add_node(node_type);
98 node.custom1 = *type;
99 params.update_and_connect_available_socket(node, name);
100 });
101 }
102 }
103}
104
105static float compute_variance(const Span<float> data, const float mean)
106{
107 if (data.size() <= 1) {
108 return 0.0f;
109 }
110
111 float sum_of_squared_differences = std::accumulate(
112 data.begin(), data.end(), 0.0f, [mean](float accumulator, float value) {
113 float difference = mean - value;
114 return accumulator + difference * difference;
115 });
116
117 return sum_of_squared_differences / data.size();
118}
119
121{
122 if (data.is_empty()) {
123 return 0.0f;
124 }
125
126 const float median = data[data.size() / 2];
127
128 /* For spans of even length, the median is the average of the middle two elements. */
129 if (data.size() % 2 == 0) {
130 return (median + data[data.size() / 2 - 1]) * 0.5f;
131 }
132 return median;
133}
134
136{
137 GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
138 const bNode &node = params.node();
139 const eCustomDataType data_type = eCustomDataType(node.custom1);
140 const AttrDomain domain = AttrDomain(node.custom2);
141 Vector<const GeometryComponent *> components = geometry_set.get_components();
142
143 const Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
144
145 switch (data_type) {
146 case CD_PROP_FLOAT: {
147 const Field<float> input_field = params.extract_input<Field<float>>("Attribute");
149 for (const GeometryComponent *component : components) {
150 const std::optional<AttributeAccessor> attributes = component->attributes();
151 if (!attributes.has_value()) {
152 continue;
153 }
154 if (attributes->domain_supported(domain)) {
155 const bke::GeometryFieldContext field_context{*component, domain};
156 fn::FieldEvaluator data_evaluator{field_context, attributes->domain_size(domain)};
157 data_evaluator.add(input_field);
158 data_evaluator.set_selection(selection_field);
159 data_evaluator.evaluate();
160 const VArray<float> component_data = data_evaluator.get_evaluated<float>(0);
161 const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
162
163 const int next_data_index = data.size();
164 data.resize(next_data_index + selection.size());
165 MutableSpan<float> selected_data = data.as_mutable_span().slice(next_data_index,
166 selection.size());
167 array_utils::gather(component_data, selection, selected_data);
168 }
169 }
170
171 float mean = 0.0f;
172 float median = 0.0f;
173 float sum = 0.0f;
174 float min = 0.0f;
175 float max = 0.0f;
176 float range = 0.0f;
177 float standard_deviation = 0.0f;
178 float variance = 0.0f;
179 const bool sort_required = params.output_is_required("Min") ||
180 params.output_is_required("Max") ||
181 params.output_is_required("Range") ||
182 params.output_is_required("Median");
183 const bool sum_required = params.output_is_required("Sum") ||
184 params.output_is_required("Mean");
185 const bool variance_required = params.output_is_required("Standard Deviation") ||
186 params.output_is_required("Variance");
187
188 if (data.size() != 0) {
189 if (sort_required) {
190 std::sort(data.begin(), data.end());
191 median = median_of_sorted_span(data);
192
193 min = data.first();
194 max = data.last();
195 range = max - min;
196 }
197 if (sum_required || variance_required) {
199 mean = sum / data.size();
200
201 if (variance_required) {
202 variance = compute_variance(data, mean);
203 standard_deviation = std::sqrt(variance);
204 }
205 }
206 }
207
208 if (sum_required) {
209 params.set_output("Sum", sum);
210 params.set_output("Mean", mean);
211 }
212 if (sort_required) {
213 params.set_output("Min", min);
214 params.set_output("Max", max);
215 params.set_output("Range", range);
216 params.set_output("Median", median);
217 }
218 if (variance_required) {
219 params.set_output("Standard Deviation", standard_deviation);
220 params.set_output("Variance", variance);
221 }
222 break;
223 }
224 case CD_PROP_FLOAT3: {
225 const Field<float3> input_field = params.extract_input<Field<float3>>("Attribute");
227 for (const GeometryComponent *component : components) {
228 const std::optional<AttributeAccessor> attributes = component->attributes();
229 if (!attributes.has_value()) {
230 continue;
231 }
232 if (attributes->domain_supported(domain)) {
233 const bke::GeometryFieldContext field_context{*component, domain};
234 fn::FieldEvaluator data_evaluator{field_context, attributes->domain_size(domain)};
235 data_evaluator.add(input_field);
236 data_evaluator.set_selection(selection_field);
237 data_evaluator.evaluate();
238 const VArray<float3> component_data = data_evaluator.get_evaluated<float3>(0);
239 const IndexMask selection = data_evaluator.get_evaluated_selection_as_mask();
240
241 const int next_data_index = data.size();
242 data.resize(data.size() + selection.size());
243 MutableSpan<float3> selected_data = data.as_mutable_span().slice(next_data_index,
244 selection.size());
245 array_utils::gather(component_data, selection, selected_data);
246 }
247 }
248
249 float3 median{0};
250 float3 min{0};
251 float3 max{0};
252 float3 range{0};
253 float3 sum{0};
254 float3 mean{0};
255 float3 variance{0};
256 float3 standard_deviation{0};
257 const bool sort_required = params.output_is_required("Min") ||
258 params.output_is_required("Max") ||
259 params.output_is_required("Range") ||
260 params.output_is_required("Median");
261 const bool sum_required = params.output_is_required("Sum") ||
262 params.output_is_required("Mean");
263 const bool variance_required = params.output_is_required("Standard Deviation") ||
264 params.output_is_required("Variance");
265
266 Array<float> data_x;
267 Array<float> data_y;
268 Array<float> data_z;
269 if (sort_required || variance_required) {
270 data_x.reinitialize(data.size());
271 data_y.reinitialize(data.size());
272 data_z.reinitialize(data.size());
273 for (const int i : data.index_range()) {
274 data_x[i] = data[i].x;
275 data_y[i] = data[i].y;
276 data_z[i] = data[i].z;
277 }
278 }
279
280 if (data.size() != 0) {
281 if (sort_required) {
282 std::sort(data_x.begin(), data_x.end());
283 std::sort(data_y.begin(), data_y.end());
284 std::sort(data_z.begin(), data_z.end());
285
286 const float x_median = median_of_sorted_span(data_x);
287 const float y_median = median_of_sorted_span(data_y);
288 const float z_median = median_of_sorted_span(data_z);
289 median = float3(x_median, y_median, z_median);
290
291 min = float3(data_x.first(), data_y.first(), data_z.first());
292 max = float3(data_x.last(), data_y.last(), data_z.last());
293 range = max - min;
294 }
295 if (sum_required || variance_required) {
297 mean = sum / data.size();
298
299 if (variance_required) {
300 const float x_variance = compute_variance(data_x, mean.x);
301 const float y_variance = compute_variance(data_y, mean.y);
302 const float z_variance = compute_variance(data_z, mean.z);
303 variance = float3(x_variance, y_variance, z_variance);
304 standard_deviation = float3(
305 std::sqrt(variance.x), std::sqrt(variance.y), std::sqrt(variance.z));
306 }
307 }
308 }
309
310 if (sum_required) {
311 params.set_output("Sum", sum);
312 params.set_output("Mean", mean);
313 }
314 if (sort_required) {
315 params.set_output("Min", min);
316 params.set_output("Max", max);
317 params.set_output("Range", range);
318 params.set_output("Median", median);
319 }
320 if (variance_required) {
321 params.set_output("Standard Deviation", standard_deviation);
322 params.set_output("Variance", variance);
323 }
324 break;
325 }
326 default:
327 break;
328 }
329}
330
331static void node_rna(StructRNA *srna)
332{
334 srna,
335 "data_type",
336 "Data Type",
337 "The data type the attribute is converted to before calculating the results",
341 [](bContext * /*C*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/, bool *r_free) {
342 *r_free = true;
344 return ELEM(item.value, CD_PROP_FLOAT, CD_PROP_FLOAT3);
345 });
346 });
347
349 "domain",
350 "Domain",
351 "Which domain to read the data from",
354 int(AttrDomain::Point),
355 nullptr,
356 true);
357}
358
359static void node_register()
360{
361 static blender::bke::bNodeType ntype;
362 geo_node_type_base(&ntype, "GeometryNodeAttributeStatistic", GEO_NODE_ATTRIBUTE_STATISTIC);
363 ntype.ui_name = "Attribute Statistic";
364 ntype.ui_description =
365 "Calculate statistics about a data set from a field evaluated on a geometry";
366 ntype.enum_name_legacy = "ATTRIBUTE_STATISTIC";
368 ntype.initfunc = node_init;
369 ntype.declare = node_declare;
374
375 node_rna(ntype.rna_ext.srna);
376}
378
379} // namespace blender::nodes::node_geo_attribute_statistic_cc
#define NODE_CLASS_ATTRIBUTE
Definition BKE_node.hh:462
#define GEO_NODE_ATTRIBUTE_STATISTIC
#define ELEM(...)
#define CTX_N_(context, msgid)
#define BLT_I18NCONTEXT_ID_NODETREE
#define IFACE_(msgid)
@ CD_PROP_FLOAT
@ CD_PROP_FLOAT3
@ SOCK_IN
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_FLOAT
@ SOCK_ROTATION
@ SOCK_RGBA
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
#define UI_ITEM_NONE
BMesh const char void * data
static T sum(const btAlignedObjectArray< T > &items)
const T * end() const
Definition BLI_array.hh:325
const T & last(const int64_t n=0) const
Definition BLI_array.hh:296
const T & first() const
Definition BLI_array.hh:281
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:419
const T * begin() const
Definition BLI_array.hh:321
void set_selection(Field< bool > selection)
Definition FN_field.hh:383
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
IndexMask get_evaluated_selection_as_mask() const
Definition field.cc:817
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
Vector< SocketDeclaration * > inputs
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
T compute_sum(const Span< T > data)
void gather(const GVArray &src, const IndexMask &indices, GMutableSpan dst, int64_t grain_size=4096)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
static float median_of_sorted_span(const Span< float > data)
static std::optional< eCustomDataType > node_type_from_other_socket(const bNodeSocket &socket)
static float compute_variance(const Span< float > data, const float mean)
static void node_gather_link_searches(GatherLinkSearchOpParams &params)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
void search_link_ops_for_declarations(GatherLinkSearchOpParams &params, Span< SocketDeclaration * > declarations)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
const EnumPropertyItem * enum_items_filter(const EnumPropertyItem *original_item_array, FunctionRef< bool(const EnumPropertyItem &item)> fn)
VecBase< float, 3 > float3
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
const char * name
const EnumPropertyItem rna_enum_attribute_domain_items[]
const EnumPropertyItem rna_enum_attribute_type_items[]
#define min(a, b)
Definition sort.cc:36
StructRNA * srna
int16_t custom1
int16_t custom2
Vector< const GeometryComponent * > get_components() const
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeGatherSocketLinkOperationsFunction gather_link_search_ops
Definition BKE_node.hh:378
NodeDeclareFunction declare
Definition BKE_node.hh:362
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
#define N_(msgid)
PointerRNA * ptr
Definition wm_files.cc:4238