A C++ header-only library to read an .asd
file generated by high-speed AFM,
depending only on the C++ standard library.
libasd provides a binding for Python3 depending on pybind11.
The python binding part depends on pybind11.
To use it, firstly build it, and add PYTHONPATH
.
Build it with CMake. CMake will automatically download
pybind11 as a git submodule, so it can be built by running cmake
and make
.
$ mkdir build
$ cd build
$ cmake ..
$ make
It also build test codes. you can run tests by
$ make test # optional
To import
libasd, you have to set PYTHONPATH
to the shared object (.so
)
file that is generated under the build/python/
directory.
$ export PYTHONPATH=$PYTHONPATH:/path/to/libasd/build/python
Or, you can dynamically add the path in your python code.
import sys
sys.path.append("path/to/libasd/build/python") # add the location of .so file
import libasd
After set your PYTHONPATH
, you can use the library in the following way.
import libasd
import numpy as np
import matplotlib.pyplot as plt
data = libasd.read_asd("example.asd")
print("version = {}" .format(data.header.file_version))
print("image size = {}x{}" .format(data.header.x_pixel, data.header.y_pixel))
print("there are {} frames.".format(len(data.frames)))
frame_0 = np.array(data.frames[0].data, copy = False)
plt.imshow(frame_0)
plt.show()
Unlike C++ version, Python version requires neither a version information nor a channel information. By reading the file, it extracts the information at a runtime, dynamically. C++ version requires both of them in order to determine types at a compile time, statically.
Because libasd aims the interoperability with NumPy, libasd returns a frame object
by using Buffer protocol
relying on pybind11.
It enables users to cast a libasd.Frame
object to NumPy Array without
copying all the elements in the frame (don't forget to set copy = False
).
import libasd
import numpy as np
# read the data
data = libasd.read_asd("example.asd")
# cast frame data into numpy Array
frame_0 = np.array(data.frames[0].data, copy = False)
To manipulate the frame datas, it is recommended to cast the data to the NumPy Arrays.
Python binding provides a method to read a header information only.
import libasd
header = libasd.read_header("example.asd")
print("version = {}" .format(header.file_version))
print("image size = {}x{}".format(header.x_pixel, header.y_pixel))
And it also supports reading raw data. read_asd
returns frame data after
converting the raw data to the height information, but read_raw_data
returns
frames without any conversion.
import libasd
raw_data = libasd.read_raw_data("example.asd")
raw_frame_0 = np.array(raw_data.frames[0].data, copy = False)
plt.imshow(raw_frame_0)
plt.show()
libasd is a header-only library, so you don't need to build anything if you use it in your C++ code.
The only thing you have to do is adding path/to/libasd
to your include path
when you compile your code.
$ g++ -I/path/to/libasd -std=c++11 your_code.cpp
Then you can read the data in the way described below.
#include <libasd/libasd.hpp>
#include <fstream>
#include <iostream>
int main()
{
std::ifstream ifs("example.asd");
const auto data = asd::read_asd<double>(ifs);
std::cout << "x_pixel = " << data.header.x_pixel << '\n';
std::cout << "y_pixel = " << data.header.y_pixel << '\n';
for(auto const& frame : data.frames)
{
for(auto const& line : frame)
{
for(auto const& pixel : line)
{
std::cout << pixel << ','; // height [nm] for topography, ...
}
std::cout << '\n';
}
std::cout << "\n\n";
}
std::cout << std::flush;
return 0;
}
Here you can access each frame, line, and pixel intuitively by using range-based for loops.
You can set file version and channel as a template parameter.
const auto data = asd::read_asd<double, asd::ch<2>, asd::ver<0>>(ifs);
By default, these are set as channel 1, version 1 respectively.
You can access to each pixel in each line by index.
const std::size_t x_pixel = data.header.x_pixel;
const std::size_t y_pixel = data.header.y_pixel;
for(const auto& frame : data.frames)
{
for(std::size_t y=0; y<y_pixel; ++y)
{
for(std::size_t x=0; x<x_pixel; ++x)
{
std::cout << data[y][x]; // note: not [x][y].
}
std::cout << '\n';
}
std::cout << "\n\n";
}
Be careful with the order of index. Frame[y]
returns a Line
at y
.
So first you must specify y
value of the pixel.
libasd provides asd::read_header
function (libasd.read_header
in python).
It reads only file-header information.
#include <libasd/libasd.hpp>
#include <fstream>
#include <iostream>
int main()
{
std::ifstream ifs("example.asd");
const auto data = asd::read_header(ifs);
// If you want to set file version information,
// data = libasd.read_header<asd::ver<2>>("example.asd");
// file version is set as asd::ver<1> by default.
return 0;
}
import libasd
data = libasd.read_header("example.asd");
# If you want to set file version information,
# data = libasd.read_header("example.asd", version = 2);
It does not need the channel information because header format does not depend on the number of channels.
You can pass a char const*
to read_asd
function in exactly the same way as
iostreams.
It contains pixels in one frame data by one, contiguous container.
You might think that libasd contains a frame as an array of arrays because
you can access to the pixel by using this form frame[y][x]
.
So you might be afraid of the performance loss that is owing to a cache-miss
while you access to each line. But it is NOT true.
To make the usage easier, libasd provides a proxy class to access each lines. It wraps frame class and enable you to access a particular line by using container-like interface.
You can use raw std::vector<pixel_type>::(const_)iterator
in the following
way if you want.
const auto frame = data.frames.front();
for(auto iter = frame.raw_begin(), iend = frame.raw_end(); iter != iend; ++iter)
{
std::cerr << *iter << ' '; // you can traverse all the pixel in one frame!
}
If you implemented or found a container or an allocator that has a great feature,
you may want to use it with libasd instead of std::vector<T, std::allocator<T>>
.
In libasd, you can specify the container used to store pixel data by passing the
special struct as a template parameter. NOTE: The container should be
randomly accessible(by using operator[]
). But ContiguousContainer
concept is
not required (e.g. std::deque
can be used as a backend container).
For example, asd::container::vec
that is used by default is defined as follows.
namespace asd {
namespace container {
struct vec
{
template<typename T>
struct rebind
{
typedef std::vector<T, std::allocator<T>> other;
};
};
template<typename T, typename Alloc>
struct container_traits<std::vector<T, Alloc>>
{
using ptr_accessibility = std::true_type;
using value_type = T;
};
} // container
} // asd
libasd uses vec
class to define container in the same way as STL does to define
allocators.
typedef typename vec::template rebind<int>::other int_array;
The struct container_traits
provides a tag to dispatch utility functions.
If the container has an interface to access the pointer that points
the first element as a traditional C-array, ptr_accessibility
is set as
std::true_type
, otherwise, std::false_type
.
Additionally, to deal with different interfaces of containers, libasd has some helper functions. If it is needed (the container has a different interface from standard containers), you should overload these functions.
Fortunately, if your awesome container has member functions that are named in
the same way as STL containers (e.g. data()
, size()
, resize()
and
clear()
), libasd automatically finds and uses them.
// example: add overload for std::vector.
namespace asd {
namespace container {
template<typename T, typename Alloc>
inline T const* get_ptr(const std::vector<T, Alloc>& v) noexcept
{
return v.data();
}
template<typename T, typename Alloc>
inline std::size_t size(const std::vector<T, Alloc>& v) noexcept
{
return v.size();
}
template<typename T, typename Alloc>
inline void resize(std::vector<T, Alloc>& v, const std::size_t N)
{
return v.resize(N);
}
template<typename T, typename Alloc>
inline void clear(std::vector<T, Alloc>& v)
{
return v.clear();
}
} // container
} // asd
After implementing these structs and functions,
you can use your awesome container/allocator class with libasd by passing
your dispatcher to read_asd
function.
By default, asd::container::vec
and asd::container::deq
are defined in
libasd/container_dispatcher.hpp
.
They correspond to std::vector
and std::deque
, respectively.
Additionally, you can find asd::container::boost_vec
,
asd::container::boost_static_vec
, asd::container::boost_small_vec
and asd::container::boost_deq
in the file
libasd/boost/container_dispatcher.hpp
.
It is not included by default, but you can manually include this file.
Then you can use containers provided by the Boost.Container library if you
already installed it.
Author
- Toru Niina
cite as
Toru Niina. (2018, March 28). ToruNiina/libasd v1.0.0 (Version v1.0.0). Zenodo. http://doi.org/10.5281/zenodo.1209119
This product is licensed under the terms of the MIT License.
- Copyright (c) 2017 Toru Niina
All rights reserved.