Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
The container registry cleanup task is now completed and the registry can be used normally.
Open sidebar
IP
elsa
Commits
a35fe939
Commit
a35fe939
authored
Sep 01, 2021
by
Jonas Jelten
🥕
Browse files
pretty printing for DataContainer
parent
bbf3e9c8
Changes
4
Hide whitespace changes
Inline
Side-by-side
elsa/core/CMakeLists.txt
View file @
a35fe939
...
...
@@ -4,6 +4,7 @@ set(MODULE_HEADERS
Backtrace.h
Cloneable.h
Utilities/Badge.hpp
Utilities/DataContainerFormatter.hpp
Utilities/Statistics.hpp
Utilities/TypeCasts.hpp
Descriptors/DataDescriptor.h
...
...
elsa/core/DataContainer.cpp
View file @
a35fe939
#include
"DataContainer.h"
#include
"DataContainerFormatter.hpp"
#include
"DataHandlerCPU.h"
#include
"DataHandlerMapCPU.h"
#include
"BlockDescriptor.h"
...
...
@@ -495,6 +496,13 @@ namespace elsa
return
_dataHandlerType
;
}
template
<
typename
data_t
>
void
DataContainer
<
data_t
>::
format
(
std
::
ostream
&
os
)
const
{
DataContainerFormatter
<
data_t
>
fmt
;
fmt
.
format
(
os
,
*
this
);
}
template
<
typename
data_t
>
DataContainer
<
data_t
>
DataContainer
<
data_t
>::
loadToCPU
()
{
...
...
elsa/core/DataContainer.h
View file @
a35fe939
...
...
@@ -435,6 +435,9 @@ namespace elsa
template
<
bool
GPU
,
class
Operand
,
std
::
enable_if_t
<
isDataContainer
<
Operand
>,
int
>>
friend
constexpr
auto
evaluateOrReturn
(
Operand
const
&
operand
);
/// write a pretty-formatted string representation to stream
void
format
(
std
::
ostream
&
os
)
const
;
/**
* @brief Factory function which returns GPU based DataContainers
*
...
...
@@ -491,6 +494,15 @@ namespace elsa
bool
canAssign
(
DataHandlerType
handlerType
);
};
/// pretty output formatting.
/// for configurable output, use `DataContainerFormatter` directly.
template
<
typename
T
>
std
::
ostream
&
operator
<<
(
std
::
ostream
&
os
,
const
elsa
::
DataContainer
<
T
>&
dc
)
{
dc
.
format
(
os
);
return
os
;
}
/// Concatenate two DataContainers to one (requires copying of both)
template
<
typename
data_t
>
DataContainer
<
data_t
>
concatenate
(
const
DataContainer
<
data_t
>&
dc1
,
...
...
elsa/core/Utilities/DataContainerFormatter.hpp
0 → 100644
View file @
a35fe939
#pragma once
#include
<elsaDefines.h>
#include
<iomanip>
#include
<iostream>
#include
<iterator>
#include
<sstream>
#include
<string>
#include
<string_view>
#include
<type_traits>
namespace
elsa
{
/**
* @brief Pretty printing for elsa::DataContainer.
*
* Inspired by numpy's formatter, supports n-dimensional string representation
* of stored values in a `DataContainer`.
*
* @author Jonas Jelten - initial code
*
* @tparam data_t: datacontainer element type
*/
template
<
typename
data_t
>
class
DataContainerFormatter
{
public:
/**
* Formatting output configuration.
*/
struct
format_config
{
/// for too many elements, abbreviate the output
bool
summary_enabled
=
true
;
/// number of summary items to display - this also triggers the summary enabling
/// if theres more than 2 * summary_items elements.
index_t
summary_items
=
6
;
/// what is inserted between the summary items
std
::
string
summary_elem
=
"..."
;
// what is inserted vertically between summary items
std
::
string
summary_elem_vertical
=
"\u22EE"
;
// what's inserted between elements and newlines
std
::
string
separator
=
", "
;
};
/**
* Create a formatter with default config.
*/
DataContainerFormatter
()
:
config
{
this
->
get_default_format_config
()}
{}
/**
* Create a formatter with custom config.
*/
DataContainerFormatter
(
const
format_config
&
config
)
:
config
{
config
}
{}
/**
* get the default formatting config.
* the class defaults are adjusted due to the `data_t`.
*/
format_config
get_default_format_config
()
{
format_config
cfg
;
if
constexpr
(
isComplex
<
data_t
>
)
{
cfg
.
summary_items
/=
2
;
}
return
cfg
;
}
/// stream a pretty format to is using the current configuration.
void
format
(
std
::
ostream
&
os
,
const
elsa
::
DataContainer
<
data_t
>&
dc
)
{
using
namespace
std
::
string_literals
;
auto
dims
=
dc
.
getDataDescriptor
().
getNumberOfDimensions
();
auto
shape
=
dc
.
getDataDescriptor
().
getNumberOfCoefficientsPerDimension
();
os
<<
"DataContainer<dims="
<<
dims
<<
", shape=("
<<
shape
.
transpose
()
<<
")>"
<<
std
::
endl
;
auto
format_element
=
this
->
get_element_formatter
<
data_t
>
(
dc
);
std
::
function
<
void
(
std
::
ostream
&
stream
,
index_t
current_dim
,
IndexVector_t
&
index
,
std
::
string_view
indent_prefix
)
>
recurser
;
recurser
=
[
this
,
&
recurser
,
&
dc
,
&
dims
,
&
shape
,
&
format_element
](
std
::
ostream
&
os
,
index_t
current_dim
,
IndexVector_t
&
index
,
std
::
string_view
hanging_indent
)
{
index_t
dims_left
=
dims
-
current_dim
;
index_t
next_dim
=
current_dim
+
1
;
if
(
dims_left
==
0
)
{
// get the actual element, recursion terminator! \o/
format_element
(
os
,
dc
(
index
));
return
;
}
std
::
string
next_hanging_indent
=
std
::
string
(
hanging_indent
)
+
" "
s
;
index_t
dim_size
=
shape
(
current_dim
);
auto
backidx
=
[
dim_size
](
index_t
idx
)
{
return
dim_size
-
idx
;
};
index_t
leading_items
,
trailing_items
;
bool
do_summary
=
(
this
->
config
.
summary_enabled
and
2
*
this
->
config
.
summary_items
<
dim_size
);
if
(
do_summary
)
{
leading_items
=
this
->
config
.
summary_items
;
trailing_items
=
this
->
config
.
summary_items
;
}
else
{
leading_items
=
0
;
trailing_items
=
dim_size
;
}
os
<<
"["
;
// last dimension, i.e. the rows
if
(
dims_left
==
1
)
{
// TODO c++20 use views::iota
for
(
index_t
i
=
0
;
i
<
leading_items
;
i
++
)
{
index
(
current_dim
)
=
i
;
recurser
(
os
,
next_dim
,
index
,
next_hanging_indent
);
os
<<
this
->
config
.
separator
;
}
if
(
do_summary
)
{
os
<<
this
->
config
.
summary_elem
<<
this
->
config
.
separator
;
}
for
(
index_t
i
=
trailing_items
;
i
>
1
;
i
--
)
{
index
(
current_dim
)
=
backidx
(
i
);
recurser
(
os
,
next_dim
,
index
,
next_hanging_indent
);
os
<<
this
->
config
.
separator
;
}
index
(
current_dim
)
=
backidx
(
1
);
recurser
(
os
,
next_dim
,
index
,
next_hanging_indent
);
}
else
{
// newlines between rows
// the more dimensions, the more newlines.
auto
line_separator
=
(
std
::
string
(
this
->
rstrip
(
this
->
config
.
separator
))
+
std
::
string
(
dims_left
-
1
,
'\n'
));
for
(
index_t
i
=
0
;
i
<
leading_items
;
i
++
)
{
index
(
current_dim
)
=
i
;
if
(
i
!=
0
)
{
os
<<
hanging_indent
;
}
recurser
(
os
,
next_dim
,
index
,
next_hanging_indent
);
os
<<
line_separator
;
}
if
(
do_summary
)
{
os
<<
hanging_indent
<<
" "
<<
this
->
config
.
summary_elem_vertical
<<
line_separator
;
}
// remaining but the last element
for
(
index_t
i
=
trailing_items
;
i
>
1
;
i
--
)
{
index
(
current_dim
)
=
backidx
(
i
);
if
(
do_summary
or
i
!=
trailing_items
)
{
os
<<
hanging_indent
;
}
recurser
(
os
,
next_dim
,
index
,
next_hanging_indent
);
os
<<
line_separator
;
}
// indent for the current dim's last entry
// we skip it if it's the only entry
if
(
trailing_items
>
1
)
{
os
<<
hanging_indent
;
}
// print the last element
index
(
current_dim
)
=
backidx
(
1
);
recurser
(
os
,
next_dim
,
index
,
next_hanging_indent
);
}
os
<<
"]"
;
};
// we'll modify this index on the fly when we walk over the datacontainer
IndexVector_t
dc_index
{
dims
};
auto
prefix
=
" "
s
;
// skip over initial [
recurser
(
os
,
0
,
dc_index
,
prefix
);
}
private:
/**
* adjust a given string_view and remove all whitespace from the right side.
*/
std
::
string_view
rstrip
(
std
::
string_view
text
)
{
auto
end_it
=
std
::
rbegin
(
text
);
while
(
end_it
!=
std
::
rend
(
text
)
and
*
end_it
==
' '
)
{
++
end_it
;
}
// TODO c++20: use stringview iterator constructor directly
const
char
*
start
=
&
(
*
std
::
begin
(
text
));
const
char
*
end
=
&
(
*
end_it
)
+
1
;
ssize_t
len
=
end
-
start
;
if
(
len
<
0
)
{
throw
elsa
::
InternalError
{
"rstrip length sanity check failed"
};
}
return
std
::
string_view
(
start
,
static_cast
<
size_t
>
(
len
));
}
/**
* Given a DataContainer, generate a function that will format one element beautifully.
* This element is padded to the max width of the other elements.
*
* @param dc: data to get the formatter for.
*
* TODO add new features:
* unique: display elements in such a way they are uniquely distinguishable
* (dynamically adjust precision)
* precision: if unique is false, use this float precision.
*/
template
<
typename
T
>
std
::
function
<
std
::
ostream
&
(
std
::
ostream
&
os
,
const
T
&
elem
)
>
get_element_formatter
(
const
elsa
::
DataContainer
<
T
>&
dc
)
{
if
constexpr
(
elsa
::
isComplex
<
T
>
)
{
// format both components independently
auto
real_formatter
=
get_element_formatter
(
DataContainer
<
T
>
{
real
(
dc
)});
auto
imag_formatter
=
get_element_formatter
(
DataContainer
<
T
>
{
imag
(
dc
)});
return
[
real_formatter
,
imag_formatter
](
std
::
ostream
&
os
,
const
T
&
elem
)
->
auto
&
{
real_formatter
(
os
,
elem
.
real
());
os
<<
"+"
;
imag_formatter
(
os
,
elem
.
imag
());
os
<<
"j"
;
return
os
;
};
}
else
if
constexpr
(
std
::
is_floating_point_v
<
T
>
)
{
// TODO: handle non-finite (not nan, inf) elements
// TODO: align stuff at the . and do right and left padding
bool
suppress_small
=
true
;
bool
use_exp
=
false
;
T
val_max
=
dc
.
maxElement
();
T
val_min
=
dc
.
minElement
();
if
(
val_max
>
1e7
or
(
not
suppress_small
and
(
val_min
<
0.0001
or
val_max
/
val_min
>
1000.0
)))
{
use_exp
=
true
;
}
// TODO: possible optimization - no underlying string needed,
// could be a /dev/null-like storage,
// since we only use tellp.
std
::
ostringstream
teststream
;
if
(
use_exp
)
{
teststream
<<
std
::
scientific
;
}
else
{
teststream
<<
std
::
defaultfloat
;
}
// setw wants int...
int
maxlen
=
0
;
for
(
elsa
::
index_t
idx
=
0
;
idx
<
dc
.
getSize
();
++
idx
)
{
teststream
.
str
(
""
);
teststream
.
clear
();
auto
&&
elem
=
dc
[
idx
];
teststream
<<
elem
;
auto
len
=
static_cast
<
int
>
(
teststream
.
tellp
());
if
(
len
>
maxlen
)
{
maxlen
=
len
;
}
}
auto
streamflags
=
teststream
.
flags
();
return
[
maxlen
,
streamflags
](
std
::
ostream
&
os
,
const
T
&
elem
)
->
auto
&
{
os
.
flags
(
streamflags
);
os
<<
std
::
setw
(
maxlen
);
os
<<
elem
;
return
os
;
};
}
else
{
// setw wants int, string::size returns size_t. great.
int
maxlen
=
static_cast
<
int
>
(
std
::
max
(
std
::
to_string
(
dc
.
maxElement
()).
size
(),
std
::
to_string
(
dc
.
minElement
()).
size
()));
return
[
maxlen
](
std
::
ostream
&
os
,
const
T
&
elem
)
->
auto
&
{
auto
&&
elem_str
=
std
::
to_string
(
elem
);
os
<<
std
::
setw
(
maxlen
);
os
<<
std
::
to_string
(
elem
);
return
os
;
};
}
}
/// formatting output configuration
format_config
config
;
};
}
// namespace elsa
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment