Unverified Commit 18a1b8f3 authored by Bevan Cheeseman's avatar Bevan Cheeseman Committed by GitHub
Browse files

Merge pull request #97 from cheesema/developPython

Develop python
parents 72739317 0890e0e6
......@@ -114,12 +114,28 @@ All examples except Example_get_apr require an already produced APR, such as tho
For tutorial on how to use the examples, and explanation of data-structures see [the library guide](./docs/lib_guide.pdf).
## Python support
Basic functionality is supported in Python through wrappers. To build the python module,
use the CMake option
`-DAPR_BUILD_PYTHON_WRAPPERS=ON`
Example usage of the available functionality:
| Example | How to ... |
|:--|:--|
| [Example_get_apr_from_array](./examples/python_examples/Example_get_apr_from_array.py) | create an APR from an ndarray and store as hdf5. |
| [Example_get_apr_from_file](./examples/python_examples/Example_get_apr_from_file.py) | create an APR from a TIFF and store as hdf5. |
| [Example_reconstruct_image](./examples/python_examples/Example_reconstruct_image.py) | read in an APR and reconstruct a pixel image |
Note that you may have to manually change the `sys.path.insert()` statements before `import pyApr` in these scripts to insert your build folder.
## Coming soon
* more examples for APR-based filtering and segmentation
* deployment of the Java wrappers to Maven Central so they can be used in your project directly
* support for loading the APR in [Fiji](https://fiji.sc), including [scenery-based](https://github.com/scenerygraphics/scenery) 3D rendering
* basic python wrapper support
* improved java wrapper support
* CUDA GPU-accelerated APR generation and processing
......
# Joel Jonsson, 2018
import os, sys
import argparse
import numpy as np
from TIFFreadwrite import readTiff
sys.path.insert(0, "../../cmake-build-release") #change this to your build folder
import pyApr
"""
Example script that reads in a .tif image to a numpy array and computes the APR from it. The resulting APR
is written to a HDF5 file in directory of the input file.
Usage: python Example_get_apr_from_file.py -d /home/user/images/ -i myImage.tif -o myAPR
"""
def main(args):
filePath = os.path.join(args.directory, args.input)
apr = pyApr.AprShort() # assuming 16 bit integers
# ----------------------- APR parameter settings ----------------------- #
pars = pyApr.APRParameters()
# Set some parameters manually
pars.Ip_th = 1000
pars.sigma_th = 100
pars.sigma_th_max = 10
pars.rel_error = 0.1
pars.lmbda = 1
# Or try using the auto_parameters
pars.auto_parameters = False
apr.set_parameters(pars)
# ----------------------- APR Conversion ----------------------- #
img = readTiff(filePath).astype(np.uint16) # numpy.ndarray
apr.get_apr_from_array(img)
outPath = os.path.join(args.directory, args.output)
apr.write_apr(outPath)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Example script: read in a TIFF image as a numpy array and compute its "
"APR. The resulting APR is saved as a HDF5 file in the given directory.")
parser.add_argument('--input', '-i', type=str, help="Name of the input .tif image")
parser.add_argument('--directory', '-d', type=str, help="Directory of the input (and output) file")
parser.add_argument('--output', '-o', type=str, help="Name of the output HDF5 file")
args = parser.parse_args()
main(args)
\ No newline at end of file
# Joel Jonsson, 2018
import os, sys
import argparse
sys.path.insert(0, "../../cmake-build-release") #change this to your build folder
import pyApr
"""
Example script that computes an APR from a .tif image and saves the APR to a HDF5 file in directory of the input file.
Usage: python Example_get_apr_from_file.py -d /Users/foo/Documents/myDir/ -i myImage.tif -o myAPR
"""
def main(args):
filePath = os.path.join(args.directory, args.input)
apr = pyApr.AprShort() # assuming 16 bit integers
# ----------------------- Parameter settings ----------------------- #
pars = pyApr.APRParameters()
# Set some parameters manually
pars.Ip_th = 1000
pars.sigma_th = 100
pars.sigma_th_max = 10
pars.rel_error = 0.1
pars.lmbda = 1
# Or try using the auto_parameters
pars.auto_parameters = False
apr.set_parameters(pars)
# ----------------------- APR Conversion ----------------------- #
apr.get_apr_from_file(filePath)
outPath = os.path.join(args.directory, args.output)
apr.write_apr(outPath)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Example script: compute the APR from a TIFF image and write the result to a HDF5 file.")
parser.add_argument('--input', '-i', type=str, help="Name of the input .tif image")
parser.add_argument('--directory', '-d', type=str, help="Directory of the input file")
parser.add_argument('--output', '-o', type=str, help="Name of the output HDF5 file")
args = parser.parse_args()
main(args)
# Joel Jonsson, 2018
import os, sys
import argparse
import numpy as np
from TIFFreadwrite import writeTiff
sys.path.insert(0, "../../cmake-build-release") #change this to your build folder
import pyApr
"""
Example script that reads an APR from a given HDF5 file, reconstructs the pixel image and writes it to .tif
Usage: python Example_reconstruct_image.py -d /home/user/images/ -i myAPR.h5 -o reconstructedImage.tif
"""
def main(args):
apr = pyApr.AprShort() # assuming 16 bit integers
# read in the APR file
filePath = os.path.join(args.directory, args.input)
apr.read_apr(filePath)
# reconstruct image
if args.smooth:
arr = apr.reconstruct_smooth() # smooth reconstruction
else:
arr = apr.reconstruct() # piecewise constant reconstruction
# convert PyPixelData object to numpy array without copy
arr = np.array(arr, copy=False)
# write image to file
outPath = os.path.join(args.directory, args.output)
writeTiff(outPath, arr, compression=None)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Example script: read an APR from a HDF5 file, reconstruct the pixel image"
" and save it as TIFF.")
parser.add_argument('--input', '-i', type=str, help="Name of the input APR file")
parser.add_argument('--directory', '-d', type=str, help="Directory of the input file")
parser.add_argument('--output', '-o', type=str, help="Name of the output .tif file")
parser.add_argument('--smooth', action='store_true', default=False, help="(Optional) use smooth reconstruction")
args = parser.parse_args()
main(args)
\ No newline at end of file
from libtiff import TIFF, TIFFfile
import numpy as np
def writeTiff(fileName, array, compression=None):
"""Write input (numpy) array to tiff file with the specified name or path"""
outTiff = TIFF.open(fileName, mode='w')
array = array.squeeze().astype(dtype=np.uint16) # cast array to uint16
ndims = len(array.shape)
if ndims == 3:
for zInd in range(array.shape[2]):
outTiff.write_image(array[:, :, zInd], compression=compression, write_rgb=False)
elif 0 < ndims < 3:
outTiff.write_image(array, compression=compression, write_rgb=False)
else:
raise ValueError('Input array must have between 1 and 3 dimensions')
outTiff.close()
return None
def readTiff(fileName):
"""
Read a tiff file into a numpy array
Usage: img = readTiff(fileName)
"""
tiff = TIFFfile(fileName)
samples, sample_names = tiff.get_samples()
outList = []
for sample in samples:
outList.append(np.copy(sample))
out = np.concatenate(outList, axis=-1)
tiff.close()
return out
\ No newline at end of file
......@@ -52,6 +52,7 @@ public:
template<typename T>
void auto_parameters(const PixelData<T> &input_img);
private:
//pointer to the APR structure so member functions can have access if they need
......@@ -138,23 +139,22 @@ inline bool APRConverter<ImageType>::get_apr_method_from_file(APR<ImageType> &aA
for (size_t i = 0; i < inputImage.mesh.size(); ++i) {
inputImage.mesh[i] = (inputImage.mesh[i] - mm.min) * maxValue / (mm.max - mm.min);
}
}
//normalize the input parameters if required
if(par.Ip_th!=-1){
std::cout << "Scaled input intensity threshold" << std::endl;
par.Ip_th = (par.Ip_th - mm.min)* maxValue / (mm.max - mm.min);
}
//normalize the input parameters if required
if(par.Ip_th!=-1){
std::cout << "Scaled input intensity threshold" << std::endl;
par.Ip_th = (par.Ip_th - mm.min)* maxValue / (mm.max - mm.min);
}
if(par.min_signal!=-1){
std::cout << "Scaled input min signal threshold" << std::endl;
par.min_signal = (par.min_signal)* maxValue / (mm.max - mm.min);
if(par.min_signal!=-1){
std::cout << "Scaled input min signal threshold" << std::endl;
par.min_signal = (par.min_signal)* maxValue / (mm.max - mm.min);
}
}
}
auto_parameters(inputImage);
//auto_parameters(inputImage);
method_timer.stop_timer();
return get_apr_method(aAPR, inputImage);
......@@ -165,8 +165,13 @@ inline bool APRConverter<ImageType>::get_apr_method_from_file(APR<ImageType> &aA
*/
template<typename ImageType> template<typename T>
inline bool APRConverter<ImageType>::get_apr_method(APR<ImageType> &aAPR, PixelData<T>& input_image) {
apr = &aAPR; // in case it was called directly
if( par.auto_parameters ) {
auto_parameters(input_image);
}
total_timer.start_timer("Total_pipeline_excluding_IO");
init_apr(aAPR, input_image);
......
......@@ -33,6 +33,8 @@ public:
float noise_sd_estimate = 0;
float background_intensity_estimate = 0;
bool auto_parameters = true;
bool normalized_input = false;
bool neighborhood_optimization = true;
......
......@@ -37,21 +37,15 @@ public:
inline bool set_neighbour_iterator(APRIterator &original_iterator, const uint8_t& direction, const uint8_t& index);
protected:
bool find_next_child(const uint8_t& direction,const uint8_t& index);
uint64_t start_index(const uint16_t level, const uint64_t offset);
uint64_t max_row_level_offset(const uint16_t x,const uint16_t z,const uint16_t num_parts);
};
uint64_t APRIterator::start_index(const uint16_t level, const uint64_t offset){
if(this->current_particle_cell.pc_offset == 0){
......
......@@ -277,6 +277,26 @@ public :
}
/**
* Initialize with provided mesh without copying.
* @param aSizeOfY
* @param aSizeOfX
* @param aSizeOfZ
* @param aArray pointer to data
*/
void init_from_mesh(int aSizeOfY, int aSizeOfX, int aSizeOfZ, T* aArray) {
y_num = aSizeOfY;
x_num = aSizeOfX;
z_num = aSizeOfZ;
size_t size = (size_t)y_num * x_num * z_num;
//TODO: fix this for python wrappers?
//meshMemory.reset(aArray);
mesh.set(aArray, size);
}
/**
* Initializes mesh with provided dimensions with default value of used type
* @param aSizeOfY
......
//
// Created by Joel Jonsson on 29.06.18.
//
#ifndef LIBAPR_PYAPR_HPP
#define LIBAPR_PYAPR_HPP
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include "ConfigAPR.h"
#include "data_structures/APR/APR.hpp"
#include "PyPixelData.hpp"
namespace py = pybind11;
// -------- Utility classes to be wrapped in python ----------------------------
template <typename T>
class PyAPR {
template<typename> friend class PyPixelData;
APR <T> apr;
public:
PyAPR () {}
/**
* Reads in the given HDF5 APR file.
*
* @param aAprFileName
*/
void read_apr(const std::string &aAprFileName) {
apr.read_apr(aAprFileName);
}
// TODO: add more versions of write_apr, with compression options etc?
/**
* Writes the APR to a HDF5 file without(?) compression.
*
* @param aOutputFile
*/
void write_apr(const std::string &aOutputFile) {
apr.write_apr("", aOutputFile);
}
/**
* Returns the piecewise constant reconstruction from the APR instance as a PyPixelData object. This can be cast
* into a numpy array without copy using 'arr = numpy.array(obj, copy=False)'.
*
* @return PyPixelData holding the reconstructed image
*/
PyPixelData<T> pc_recon() {
PixelData<T> reconstructedImage;
APRReconstruction().interp_img(apr, reconstructedImage, apr.particles_intensities);
/*
// this creates a copy...
return py::array_t<T>({reconstructedImage.x_num, reconstructedImage.y_num, reconstructedImage.z_num},
{sizeof(T) * reconstructedImage.y_num * reconstructedImage.x_num, sizeof(T), sizeof(T) * reconstructedImage.y_num},
reconstructedImage.mesh.get());
*/
//this does not copy, and can be cast to numpy.array on python side without copy (set copy=False)
return PyPixelData<T>(reconstructedImage);
}
/**
* Returns the smooth reconstruction from the APR instance as a PyPixelData object. This can be cast into a numpy
* array without copy using 'arr = numpy.array(obj, copy=False)'.
*
* @return PyPixelData holding the reconstructed image
*/
PyPixelData<T> smooth_recon() {
PixelData<T> reconstructedImage;
APRReconstruction().interp_parts_smooth(apr, reconstructedImage, apr.particles_intensities);
return PyPixelData<T>(reconstructedImage);
}
/**
* Sets the parameters for the APR conversion.
*
* @param par pyApr.APRParameters object
*/
void set_parameters(const py::object &par) {
if( py::isinstance<APRParameters>(par) ) {
apr.parameters = par.cast<APRParameters>();
} else {
throw std::invalid_argument("Input has to be a pyApr.APRParameters object.");
}
}
/**
* Computes the APR from the input python array.
*
* @param input image as python (numpy) array
*/
void get_apr_from_array(py::array &input) {
auto buf = input.request();
// Some checks, may need some polishing
if( buf.ptr == nullptr ) {
std::cerr << "Could not pass buffer in call to apr_from_array" << std::endl;
}
if ( !input.writeable() ) {
std::cerr << "Input array must be writeable" << std::endl;
}
if( !py::isinstance<py::array_t<T>>(input) ) {
throw std::invalid_argument("Conflicting types. Make sure the input array is of the same type as the AprType instance.");
}
auto *ptr = static_cast<T*>(buf.ptr);
PixelData<T> input_img;
//TODO: fix memory/ownership passing or just revert to copying?
input_img.init_from_mesh(buf.shape[2], buf.shape[1], buf.shape[0], ptr); // may lead to memory issues
apr.get_apr(input_img);
}
/**
* Reads in the provided tiff file and computes its APR. Note: parameters for the APR conversion should be set
* before by using set_parameters.
*
* @param aInputFile path to the tiff image file
*/
void get_apr_from_file(const std::string &aInputFile) {
const TiffUtils::TiffInfo aTiffFile(aInputFile);
apr.parameters.input_dir = "";
apr.parameters.input_image_name = aInputFile;
apr.get_apr();
}
};
// -------- Templated wrapper -------------------------------------------------
template <typename DataType>
void AddPyAPR(pybind11::module &m, const std::string &aTypeString) {
using AprType = PyAPR<DataType>;
std::string typeStr = "Apr" + aTypeString;
py::class_<AprType>(m, typeStr.c_str())
.def(py::init())
.def("read_apr", &AprType::read_apr, "Method to read HDF5 APR files")
.def("write_apr", &AprType::write_apr, "Writes the APR instance to a HDF5 file")
.def("reconstruct", &AprType::pc_recon, py::return_value_policy::move, "returns the piecewise constant image reconstruction as a python array")
.def("reconstruct_smooth", &AprType::smooth_recon, py::return_value_policy::move, "returns a smooth image reconstruction as a python array")
.def("set_parameters", &AprType::set_parameters, "Set parameters for APR conversion")
.def("get_apr_from_array", &AprType::get_apr_from_array, "Construct APR from input array (no copy)")
.def("get_apr_from_file", &AprType::get_apr_from_file, "Construct APR from input .tif image");
}
#endif //LIBAPR_PYAPR_HPP
//
// Created by Joel Jonsson on 29.06.18.
//
#ifndef LIBAPR_PYPIXELDATA_HPP
#define LIBAPR_PYPIXELDATA_HPP
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include "data_structures/Mesh/PixelData.hpp"
namespace py = pybind11;
// -------- Utility classes to be wrapped in python ----------------------------
/**
* Currently only using this class to return PixelData objects to python as arrays without copy. It could be made more
* complete with constructor methods and other functionality, but I don't think it is necessary.
*
* @tparam T type of mesh elements
*/
template<typename T>
class PyPixelData {
PixelData<T> image;
public:
PyPixelData() {}
PyPixelData(PixelData<T> &aInput) {
image.swap(aInput);
}
/**
* @return pointer to the mesh data
*/
T *data() {return image.mesh.get();}
/**
* @return width of the domain (number of columns)
*/
int width() const {return image.x_num;}
/**
* @return height of the domain (number of rows)
*/
int height() const {return image.y_num;}
/**
* @return depth of the domain
*/
int depth() const {return image.z_num;}
};
template<typename DataType>
void AddPyPixelData(pybind11::module &m, const std::string &aTypeString) {
using PixelDataType = PyPixelData<DataType>;
std::string typeStr = "PixelData" + aTypeString;
py::class_<PixelDataType>(m, typeStr.c_str(), py::buffer_protocol())
.def("width", &PixelDataType::width, "Returns number of columns (x)")
.def("height", &PixelDataType::height, "Returns number of rows (y)")
.def("depth", &PixelDataType::depth, "Returns the depth (z)")
.def_buffer([](PixelDataType &a) -> py::buffer_info{
return py::buffer_info(
a.data(),
sizeof(DataType),
py::format_descriptor<DataType>::format(),
3,
{a.width(), a.height(), a.depth()},
{sizeof(DataType) * a.height(), sizeof(DataType), sizeof(DataType) * a.height() * a.width()}
);
});
}
#endif //LIBAPR_PYPIXELDATA_HPP
......@@ -3,10 +3,16 @@
//
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include "ConfigAPR.h"
#include "data_structures/APR/APR.hpp"
#include "PyPixelData.hpp"