diff --git a/CMakeLists.txt b/CMakeLists.txt index 878826e..cbe9593 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,12 +29,14 @@ include(fetch_sqlite3) include(fetch_fftw3) include(fetch_libtiff) include(fetch_libjpeg) +include(fetch_libsvm) fetch_hdf5() fetch_sqlite3() fetch_fftw3() fetch_libtiff() fetch_libjpeg() +fetch_libsvm() if(SKBUILD_PLATLIB_DIR) set(PYTHON_INSTALL_DIR "${SKBUILD_PLATLIB_DIR}") @@ -77,6 +79,7 @@ file( list( APPEND SOURCES + ${XMIPP3_LIBRARY_DIR}/classification/svm_classifier.cpp ${XMIPP3_LIBRARY_DIR}/reconstruction/ctf_enhance_psd.cpp ${XMIPP3_LIBRARY_DIR}/reconstruction/ctf_estimate_from_micrograph.cpp ${XMIPP3_LIBRARY_DIR}/reconstruction/ctf_estimate_from_psd_base.cpp @@ -84,6 +87,8 @@ list( ${XMIPP3_LIBRARY_DIR}/reconstruction/ctf_estimate_from_psd_fast.cpp ${XMIPP3_LIBRARY_DIR}/reconstruction/ctf_estimate_psd_with_arma.cpp ${XMIPP3_LIBRARY_DIR}/reconstruction/fringe_processing.cpp + ${XMIPP3_LIBRARY_DIR}/reconstruction/image_rotational_pca.cpp + ${XMIPP3_LIBRARY_DIR}/reconstruction/micrograph_automatic_picking2.cpp ${XMIPP3_LIBRARY_DIR}/reconstruction/psd_estimator.cpp ${XMIPP3_LIBRARY_DIR}/data/basic_pca.cpp ${XMIPP3_LIBRARY_DIR}/data/basis.cpp @@ -98,6 +103,7 @@ list( ${XMIPP3_LIBRARY_DIR}/data/grids.cpp ${XMIPP3_LIBRARY_DIR}/data/integration.cpp ${XMIPP3_LIBRARY_DIR}/data/mask.cpp + ${XMIPP3_LIBRARY_DIR}/data/micrograph.cpp ${XMIPP3_LIBRARY_DIR}/data/morphology.cpp ${XMIPP3_LIBRARY_DIR}/data/normalize.cpp ${XMIPP3_LIBRARY_DIR}/data/numerical_tools.cpp @@ -118,6 +124,7 @@ target_link_libraries( Python::Module Python::NumPy sqlite3 + libsvm HDF5::CXX HDF5::HDF5 FFTW3::Double diff --git a/cmake/modules/fetch_libsvm.cmake b/cmake/modules/fetch_libsvm.cmake new file mode 100644 index 0000000..8ed8fd2 --- /dev/null +++ b/cmake/modules/fetch_libsvm.cmake @@ -0,0 +1,36 @@ +#*************************************************************************** +# Authors: Oier Lauzirika Zarrabeitia (oierlauzi@bizkaia.eu) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307 USA +# +# All comments concerning this program package may be sent to the +# e-mail address 'xmipp@cnb.csic.es' +# *************************************************************************** + +include(ExternalProject) + +function(fetch_libsvm) + cmake_policy(SET CMP0135 NEW) # To avoid warnings + FetchContent_Declare( + libsvm + URL https://github.com/cossorzano/libsvm/archive/refs/heads/master.tar.gz + ) + FetchContent_MakeAvailable(libsvm) + + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + add_library(libsvm STATIC ${libsvm_SOURCE_DIR}/svm.cpp) + target_include_directories(libsvm PUBLIC ${libsvm_SOURCE_DIR}) +endfunction() diff --git a/src/python_autopicker.cpp b/src/python_autopicker.cpp new file mode 100644 index 0000000..b6b416f --- /dev/null +++ b/src/python_autopicker.cpp @@ -0,0 +1,316 @@ +/*************************************************************************** + * + * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es) + * + * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + * + * All comments concerning this program package may be sent to the + * e-mail address 'xmipp@cnb.csic.es' + ***************************************************************************/ + +#include "python_autopicker.h" + +#include +#include +#include + +#include + +/***************************************************************/ +/* Autopicker */ +/**************************************************************/ + +/* Autopicker methods */ +PyMethodDef Autopicker_methods[] = + { + { "train", (PyCFunction) Autopicker_train, METH_VARARGS, + "Train the model" }, + { "autopick", (PyCFunction) Autopicker_autopick, METH_VARARGS, + "Autopick using the model" }, + { "correct", (PyCFunction) Autopicker_correct, METH_VARARGS, + "Correct the model" }, + { "setSize", (PyCFunction) Autopicker_setSize, METH_VARARGS, + "Set box size" }, + { "getParticlesThreshold", (PyCFunction) Autopicker_getParticlesThreshold, METH_NOARGS, + "Get the threshold value" }, + + { nullptr } /* Sentinel */ + };//Autopicker_methods + + +/*Autopicker Type */ +PyTypeObject AutopickerType = { + PyObject_HEAD_INIT(0) + "xmipp.Autopicker", /*tp_name*/ + sizeof(AutopickerObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)Autopicker_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + nullptr, /*tp_repr*/ + nullptr, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + "Python wrapper to Xmipp Autopicker class",/* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + nullptr, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Autopicker_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + Autopicker_new, /* tp_new */ + };//AutopickerType + +/* Destructor */ +void Autopicker_dealloc(AutopickerObject* self) +{ + self->~AutopickerObject(); // Call the destructor + Py_TYPE(self)->tp_free((PyObject*)self); +}//function Autopicker_dealloc + +/* Constructor */ +PyObject * +Autopicker_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + AutopickerObject *self = (AutopickerObject*)type->tp_alloc(type, 0); + + if (self != nullptr) + { + int particleSize; + const char *modelName; + PyObject *micrographs; + + if (!PyArg_ParseTuple(args, "isO!", &particleSize, &modelName, &PyList_Type, µgraphs)) + { + return nullptr; + } + + const auto nMicrographs = PyList_Size(micrographs); + std::vector micrographRows; + MDRowSql row; + for(Py_ssize_t i = 0; i < nMicrographs; i++) + { + PyObject *item = PyList_GetItem(micrographs, i); + if (!PyUnicode_Check(item)) { + PyErr_Format(PyExc_TypeError, "Micrograph at index %zd must be a string.", i); + return NULL; + } + const char *micrographFn = PyUnicode_AsUTF8(item); + + row.setValueFromStr(MDL_MICROGRAPH, String(micrographFn)); + micrographRows.push_back(row); + } + + const int filterNum = 6; + const int corrNum = 2; + const int nPca = 4; + self->autopicker = std::make_unique( + particleSize, + filterNum, + corrNum, + nPca, + String(modelName), + micrographRows + ); + } + return (PyObject *)self; +}//function Autopicker_new + +PyObject * +Autopicker_train(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int x; + int y; + int width; + int height; + PyObject *coordinates; + if (!PyArg_ParseTuple(args, "O!iiii", &PyList_Type, &coordinates, &x, &y, &width, &height)) + { + return NULL; + } + + MetaDataDb md; + FileName micFile, posFile; + std::vector rows; + MDRowSql row; + const auto nCoordinates = PyList_Size(coordinates); + for (Py_ssize_t i = 0; i < nCoordinates; i++) { + PyObject *item = PyList_GetItem(coordinates, i); + if (!PyTuple_Check(item)) { + PyErr_Format(PyExc_TypeError, "List item at index %zd must be a tuple.", i); + return NULL; + } + + if (PyTuple_Size(item) != 3) { + PyErr_Format(PyExc_ValueError, "Tuple at index %zd must have exactly 3 items.", i); + return NULL; + } + + PyObject *micrograph = PyTuple_GetItem(item, 0); + PyObject *x = PyTuple_GetItem(item, 1); + PyObject *y = PyTuple_GetItem(item, 2); + if (!PyUnicode_Check(micrograph) || !PyLong_Check(x) || !PyLong_Check(y)) { + PyErr_Format(PyExc_TypeError, "Tuple at index %zd must contain a string and two integers.", i); + return NULL; + } + + row.setValue(MDL_MICROGRAPH, String(PyUnicode_AsUTF8(micrograph))); + row.setValue(MDL_XCOOR, static_cast(PyLong_AsLong(x))); + row.setValue(MDL_YCOOR, static_cast(PyLong_AsLong(y))); + rows.push_back(row); + } + + Autopicker_Value(self).train(rows, false, x, y, width, height); + Py_RETURN_NONE; +} + +PyObject * +Autopicker_autopick(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *micrograph; + int percent; + if (!PyArg_ParseTuple(args, "si", µgraph, &percent)) + { + return NULL; + } + + std::vector md; + Autopicker_Value(self).automaticallySelectParticles(String(micrograph), percent, md); + const auto count = md.size(); + + PyObject *coordinates = PyList_New(count); + PyObject *costs = PyList_New(count); + if (coordinates == NULL || costs == NULL) { + Py_XDECREF(coordinates); + Py_XDECREF(costs); + return NULL; + } + + int x, y; + double c; + for (Py_ssize_t i = 0; i < count; i++) { + const auto &row = md[i]; + row.getValue(MDL_XCOOR, x); + row.getValue(MDL_YCOOR, y); + row.getValue(MDL_COST, c); + + PyObject *coordinate = Py_BuildValue("(ii)", x, y); + PyObject *cost = PyFloat_FromDouble(c); + if (coordinate == NULL || coordinate == NULL) { + Py_XDECREF(coordinate); + Py_XDECREF(cost); + Py_DECREF(coordinates); + Py_DECREF(costs); + return NULL; + } + + PyList_SetItem(coordinates, i, coordinate); + PyList_SetItem(costs, i, cost); + } + + return Py_BuildValue("(NN)", coordinates, costs); +} + +static bool coordinatesFromPyList(PyObject *list, std::vector &result) +{ + MDRowSql row; + const auto count = PyList_Size(list); + for (Py_ssize_t i = 0; i < count; i++) { + PyObject *item = PyList_GetItem(list, i); + if (!PyTuple_Check(item)) { + PyErr_Format(PyExc_TypeError, "List item at index %zd must be a tuple.", i); + return false; + } + + if (PyTuple_Size(item) != 2) { + PyErr_Format(PyExc_ValueError, "Tuple at index %zd must have exactly 2 items.", i); + return false; + } + + PyObject *x = PyTuple_GetItem(item, 0); + PyObject *y = PyTuple_GetItem(item, 1); + if (!PyLong_Check(x) || !PyLong_Check(y)) { + PyErr_Format(PyExc_TypeError, "Tuple at index %zd must contain two integers.", i); + return false; + } + + row.setValue(MDL_XCOOR, static_cast(PyLong_AsLong(x))); + row.setValue(MDL_YCOOR, static_cast(PyLong_AsLong(y))); + result.push_back(row); + } + + return true; +} + +PyObject * +Autopicker_correct(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *added; + PyObject *removed; + if (!PyArg_ParseTuple(args, "O!O!", &PyList_Type, &added, &PyList_Type, &removed)) + { + return NULL; + } + + std::vector addedRows; + std::vector removedRows; + if (!coordinatesFromPyList(added, addedRows) || !coordinatesFromPyList(removed, removedRows)) + { + return NULL; + } + + Autopicker_Value(self).correction(addedRows, removedRows); + Py_RETURN_NONE; +} + +PyObject * +Autopicker_setSize(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int size; + if (!PyArg_ParseTuple(args, "i", &size)) { + return NULL; + } + Autopicker_Value(self).setSize(size); + Py_RETURN_NONE; +} + +PyObject * +Autopicker_getParticlesThreshold(PyObject *self, PyObject*, PyObject*) +{ + const auto threshold = Autopicker_Value(self).getParticlesThreshold(); + return PyLong_FromLong(threshold); +} diff --git a/src/python_autopicker.h b/src/python_autopicker.h new file mode 100644 index 0000000..36611b0 --- /dev/null +++ b/src/python_autopicker.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * + * Authors: Oier Lauzirika Zarrabeitia + * + * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + * + * All comments concerning this program package may be sent to the + * e-mail address 'xmipp@cnb.csic.es' + ***************************************************************************/ + +#ifndef _PYTHON_AUTOPICKER_H +#define _PYTHON_AUTOPICKER_H + +#include "Python.h" +#include +#include + +extern PyObject * PyXmippError; +/***************************************************************/ +/* Autopicker */ +/***************************************************************/ + +#define Autopicker_Check(v) (((v)->ob_type == &AutopickerType)) +#define Autopicker_Value(v) ((*((AutopickerObject*)(v))->autopicker)) + +/*Autopicker Object*/ +typedef struct +{ + PyObject_HEAD + std::unique_ptr autopicker; +} +AutopickerObject; + +/* Destructor */ +void Autopicker_dealloc(AutopickerObject* self); + +/* Constructor */ +PyObject * +Autopicker_new(PyTypeObject *type, PyObject *args, PyObject *kwargs); + +PyObject * +Autopicker_train(PyObject *self, PyObject *args, PyObject *kwargs); + +PyObject * +Autopicker_autopick(PyObject *self, PyObject *args, PyObject *kwargs); + +PyObject * +Autopicker_correct(PyObject *self, PyObject *args, PyObject *kwargs); + +PyObject * +Autopicker_setSize(PyObject *self, PyObject *args, PyObject *kwargs); + +PyObject * +Autopicker_getParticlesThreshold(PyObject *self, PyObject*, PyObject*); + +extern PyMethodDef Autopicker_methods[]; +extern PyTypeObject AutopickerType; + +#endif diff --git a/src/xmippmodule.cpp b/src/xmippmodule.cpp index 3c28437..08c45c3 100644 --- a/src/xmippmodule.cpp +++ b/src/xmippmodule.cpp @@ -41,6 +41,7 @@ #include "python_program.h" #include "python_metadata.h" #include "python_symmetry.h" +#include "python_autopicker.h" #include "reconstruction/ctf_estimate_from_micrograph.h" #include "numpy/arrayobject.h" @@ -1521,6 +1522,7 @@ PyInit_xmippLib(void) { PyObject *module = PyModule_Create(&moduledef); //Check types and add to module + //INIT_TYPE(Autopicker); INIT_TYPE(FileName); INIT_TYPE(Image); INIT_TYPE(MDQuery); @@ -1528,6 +1530,7 @@ PyInit_xmippLib(void) { INIT_TYPE(Program); INIT_TYPE(SymList); INIT_TYPE(FourierProjector); + INIT_TYPE(Autopicker); //Add PyXmippError