Commit 6572fc01 authored by Thomas Vincent's avatar Thomas Vincent

Merge branch 'master' into 'update-intensity'

# Conflicts:
#   xsocs/gui/__init__.py
parents c7ea4855 aebe559b
......@@ -5,6 +5,10 @@ __pycache__/
# C extensions
*.so
# Cython generated files
xsocs/util/filt_utils/filt_utils.c
xsocs/util/filt_utils/histogramnd_lut.c
# Distribution / packaging
.Python
env/
......
......@@ -10,7 +10,7 @@ example: ./bootstrap.py ipython
__authors__ = ["Frédéric-Emmanuel Picca", "Jérôme Kieffer"]
__contact__ = "jerome.kieffer@esrf.eu"
__license__ = "MIT"
__date__ = "08/01/2018"
__date__ = "02/03/2018"
import sys
......@@ -193,6 +193,9 @@ os.chdir(home)
build = subprocess.Popen([sys.executable, "setup.py", "build"],
shell=False, cwd=os.path.dirname(os.path.abspath(__file__)))
build_rc = build.wait()
if not os.path.exists(LIBPATH):
logger.warning("`lib` directory does not exist, trying common Python3 lib")
LIBPATH = os.path.join(os.path.split(LIBPATH)[0], "lib")
os.chdir(cwd)
if build_rc == 0:
......
......@@ -32,7 +32,7 @@ Test coverage dependencies: coverage, lxml.
"""
__authors__ = ["Jérôme Kieffer", "Thomas Vincent"]
__date__ = "29/01/2018"
__date__ = "02/03/2018"
__license__ = "MIT"
import distutils.util
......@@ -76,7 +76,11 @@ def createBasicHandler():
# Use an handler compatible with unittests, else use_buffer is not working
logging.root.addHandler(createBasicHandler())
# Capture all default warnings
logging.captureWarnings(True)
import warnings
warnings.simplefilter('default')
logger = logging.getLogger("run_tests")
logger.setLevel(logging.WARNING)
......@@ -292,7 +296,12 @@ def build_project(name, root_dir):
p = subprocess.Popen([sys.executable, "setup.py", "build"],
shell=False, cwd=root_dir)
logger.debug("subprocess ended with rc= %s", p.wait())
return home
if os.path.isdir(home):
return home
alt_home = os.path.join(os.path.dirname(home), "lib")
if os.path.isdir(alt_home):
return alt_home
def import_project_module(project_name, project_dir):
......@@ -311,7 +320,8 @@ def import_project_module(project_name, project_dir):
PROJECT_NAME)
else: # Use built source
build_dir = build_project(project_name, project_dir)
if build_dir is None:
logging.error("Built project is not available !!! investigate")
sys.path.insert(0, build_dir)
logger.warning("Patched sys.path, added: '%s'", build_dir)
module = importer(project_name)
......
......@@ -2,7 +2,7 @@
# coding: utf8
# /*##########################################################################
#
# Copyright (c) 2015-2017 European Synchrotron Radiation Facility
# Copyright (c) 2015-2018 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
......@@ -25,21 +25,20 @@
# ###########################################################################*/
__authors__ = ["Jérôme Kieffer", "Thomas Vincent"]
__date__ = "11/05/2017"
__date__ = "23/04/2018"
__license__ = "MIT"
# This import is here only to fix a bug on Debian 7 with python2.7
# Without this, the system io module is not loaded from numpy.distutils
# the xsocs.io module seems to be loaded instead
import io
import sys
import os
import platform
import shutil
import logging
import glob
# io import has to be here also to fix a bug on Debian 7 with python2.7
# Without this, the system io module is not loaded from numpy.distutils.
# The silx.io module seems to be loaded instead.
import io
logging.basicConfig(level=logging.INFO)
......@@ -112,6 +111,8 @@ classifiers = ["Development Status :: 4 - Beta",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Scientific/Engineering :: Physics",
"Topic :: Software Development :: Libraries :: Python Modules",
......@@ -138,9 +139,11 @@ class build_py(_build_py):
########
class PyTest(Command):
"""Command to start tests running the script: run_tests.py -i"""
"""Command to start tests running the script: run_tests.py"""
user_options = []
description = "Execute the unittests"
def initialize_options(self):
pass
......@@ -149,7 +152,7 @@ class PyTest(Command):
def run(self):
import subprocess
errno = subprocess.call([sys.executable, 'run_tests.py', '-i'])
errno = subprocess.call([sys.executable, 'run_tests.py'])
if errno != 0:
raise SystemExit(errno)
......@@ -242,6 +245,7 @@ if sphinx is not None:
else:
TestDocCommand = SphinxExpectedCommand
# ############################# #
# numpy.distutils Configuration #
# ############################# #
......@@ -402,7 +406,7 @@ class BuildExt(build_ext):
COMPILE_ARGS_CONVERTER = {'-fopenmp': '/openmp'}
LINK_ARGS_CONVERTER = {'-fopenmp': ' '}
LINK_ARGS_CONVERTER = {'-fopenmp': ''}
description = 'Build xsocs extensions'
......@@ -452,8 +456,7 @@ class BuildExt(build_ext):
patched_exts = cythonize(
[ext],
compiler_directives={'embedsignature': True},
force=self.force_cython,
compile_time_env={"HAVE_OPENMP": self.use_openmp}
force=self.force_cython
)
ext.sources = patched_exts[0].sources
......@@ -471,6 +474,15 @@ class BuildExt(build_ext):
ext.extra_link_args = [self.LINK_ARGS_CONVERTER.get(f, f)
for f in ext.extra_link_args]
elif self.compiler.compiler_type == 'unix':
# Avoids runtime symbol collision for manylinux1 platform
# See issue #1070
extern = 'extern "C" ' if ext.language == 'c++' else ''
return_type = 'void' if sys.version_info[0] <= 2 else 'PyObject*'
ext.extra_compile_args.append(
'''-fvisibility=hidden -D'PyMODINIT_FUNC=%s__attribute__((visibility("default"))) %s ' ''' % (extern, return_type))
def is_debug_interpreter(self):
"""
Returns true if the script is executed with a debug interpreter.
......@@ -532,7 +544,6 @@ class BuildExt(build_ext):
self.patch_extension(ext)
build_ext.build_extensions(self)
################################################################################
# Clean command
################################################################################
......@@ -557,12 +568,35 @@ class CleanCommand(Clean):
path_list2.append(path)
return path_list2
def find(self, path_list):
"""Find a file pattern if directories.
Could be done using "**/*.c" but it is only supported in Python 3.5.
:param list[str] path_list: A list of path which may contains magic
:rtype: list[str]
:returns: A list of path without magic
"""
import fnmatch
path_list2 = []
for pattern in path_list:
for root, _, filenames in os.walk('.'):
for filename in fnmatch.filter(filenames, pattern):
path_list2.append(os.path.join(root, filename))
return path_list2
def run(self):
Clean.run(self)
cython_files = self.find(["*.pyx"])
cythonized_files = [path.replace(".pyx", ".c") for path in cython_files]
cythonized_files += [path.replace(".pyx", ".cpp") for path in cython_files]
# really remove the directories
# and not only if they are empty
to_remove = [self.build_base]
to_remove = self.expand(to_remove)
to_remove += cythonized_files
if not self.dry_run:
for path in to_remove:
......@@ -575,6 +609,37 @@ class CleanCommand(Clean):
except OSError:
pass
################################################################################
# Source tree
################################################################################
class SourceDistWithCython(sdist):
"""
Force cythonization of the extensions before generating the source
distribution.
To provide the widest compatibility the cythonized files are provided
without suppport of OpenMP.
"""
description = "Create a source distribution including cythonozed files (tarball, zip file, etc.)"
def finalize_options(self):
sdist.finalize_options(self)
self.extensions = self.distribution.ext_modules
def run(self):
self.cythonize_extensions()
sdist.run(self)
def cythonize_extensions(self):
from Cython.Build import cythonize
cythonize(
self.extensions,
compiler_directives={'embedsignature': True},
force=True
)
################################################################################
# Debian source tree
################################################################################
......@@ -585,10 +650,13 @@ class sdist_debian(sdist):
Tailor made sdist for debian
* remove auto-generated doc
* remove cython generated .c files
* remove cython generated .c files
* remove cython generated .cpp files
* remove .bat files
* include .l man files
"""
description = "Create a source distribution for Debian (tarball, zip file, etc.)"
@staticmethod
def get_debian_name():
import version
......@@ -623,10 +691,10 @@ class sdist_debian(sdist):
base, ext = os.path.splitext(basename)
while ext in [".zip", ".tar", ".bz2", ".gz", ".Z", ".lz", ".orig"]:
base, ext = os.path.splitext(base)
if ext:
dest = "".join((base, ext))
else:
dest = base
# if ext:
# dest = "".join((base, ext))
# else:
# dest = base
# sp = dest.split("-")
# base = sp[:-1]
# nr = sp[-1]
......@@ -642,13 +710,22 @@ class sdist_debian(sdist):
def get_project_configuration(dry_run):
"""Returns project arguments for setup"""
# Use installed numpy version as minimal required version
# This is useful for wheels to advertise the numpy version they were built with
if dry_run:
numpy_requested_version = ""
else:
from numpy.version import version as numpy_version
numpy_requested_version = ">=%s" % numpy_version
logger.info("Install requires: numpy %s", numpy_requested_version)
install_requires = [
# for most of the computation
"numpy",
"numpy%s" % numpy_requested_version,
# for the script launcher
"setuptools"]
setup_requires = ["setuptools", "numpy"]
setup_requires = ["setuptools", "numpy", "Cython>=0.21.1"]
package_data = {
'xsocs.resources': [
......@@ -671,6 +748,7 @@ def get_project_configuration(dry_run):
test_doc=TestDocCommand,
build_ext=BuildExt,
clean=CleanCommand,
sdist=SourceDistWithCython,
debian_src=sdist_debian)
if dry_run:
......@@ -717,7 +795,7 @@ def setup_package():
'clean', '--name')))
if dry_run:
# DRY_RUN implies actions which do not require dependancies, like NumPy
# DRY_RUN implies actions which do not require dependencies, like NumPy
try:
from setuptools import setup
logger.info("Use setuptools.setup")
......@@ -729,7 +807,7 @@ def setup_package():
from setuptools import setup
except ImportError:
from numpy.distutils.core import setup
logger.info("Use numpydistutils.setup")
logger.info("Use numpy.distutils.setup")
setup_kwargs = get_project_configuration(dry_run)
setup(**setup_kwargs)
......
#!/usr/bin/env python
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2015-2016 European Synchrotron Radiation Facility
# Copyright (c) 2015-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
......@@ -22,13 +23,48 @@
# THE SOFTWARE.
#
# ###########################################################################*/
"""Unique place where the version number is defined."""
"""Unique place where the version number is defined.
provides:
* version = "1.2.3" or "1.2.3-beta4"
* version_info = named tuple (1,2,3,"beta",4)
* hexversion: 0x010203B4
* strictversion = "1.2.3b4
* debianversion = "1.2.3~beta4"
* calc_hexversion: the function to transform a version_tuple into an integer
This is called hexversion since it only really looks meaningful when viewed as the
result of passing it to the built-in hex() function.
The version_info value may be used for a more human-friendly encoding of the same information.
The hexversion is a 32-bit number with the following layout:
Bits (big endian order) Meaning
1-8 PY_MAJOR_VERSION (the 2 in 2.1.0a3)
9-16 PY_MINOR_VERSION (the 1 in 2.1.0a3)
17-24 PY_MICRO_VERSION (the 0 in 2.1.0a3)
25-28 PY_RELEASE_LEVEL (0xA for alpha, 0xB for beta, 0xC for release candidate and 0xF for final)
29-32 PY_RELEASE_SERIAL (the 3 in 2.1.0a3, zero for final releases)
Thus 2.1.0a3 is hexversion 0x020100a3.
"""
from __future__ import absolute_import, print_function, division
__authors__ = ["Jérôme Kieffer"]
__license__ = "MIT"
__date__ = "18/10/2016"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__date__ = "28/02/2018"
__status__ = "production"
__docformat__ = 'restructuredtext'
__all__ = ["date", "version_info", "strictversion", "hexversion", "debianversion",
"calc_hexversion"]
# Do not copy into the source folder !
RELEASE_LEVEL_VALUE = {"dev": 0,
"alpha": 10,
"beta": 11,
"gamma": 12,
"rc": 13,
"final": 15}
MAJOR = 0
MINOR = 6
......@@ -36,29 +72,48 @@ MICRO = 0
RELEV = "dev" # <16
SERIAL = 0 # <16
date = __date__
from collections import namedtuple
_version_info = namedtuple(
"version_info", ["major", "minor", "micro", "releaselevel", "serial"])
_version_info = namedtuple("version_info", ["major", "minor", "micro", "releaselevel", "serial"])
version_info = _version_info(MAJOR, MINOR, MICRO, RELEV, SERIAL)
strictversion = version = "%d.%d.%d" % version_info[:3]
RELEASE_LEVEL_VALUE = {"dev": 0,
"alpha": 10,
"beta": 11,
"rc": 12,
"final": 15}
strictversion = version = debianversion = "%d.%d.%d" % version_info[:3]
if version_info.releaselevel != "final":
version += "-%s%s" % version_info[-2:]
debianversion += "~adev%i" % version_info[-1] if RELEV == "dev" else "~%s%i" % version_info[-2:]
prerel = "a" if RELEASE_LEVEL_VALUE.get(version_info[3], 0) < 10 else "b"
if prerel not in "ab":
prerel = "a"
strictversion += prerel + str(version_info[-1])
hexversion = version_info[4]
hexversion |= RELEASE_LEVEL_VALUE.get(version_info[3], 0) * 1 << 4
hexversion |= version_info[2] * 1 << 8
hexversion |= version_info[1] * 1 << 16
hexversion |= version_info[0] * 1 << 24
def calc_hexversion(major=0, minor=0, micro=0, releaselevel="dev", serial=0):
"""Calculate the hexadecimal version number from the tuple version_info:
:param major: integer
:param minor: integer
:param micro: integer
:param relev: integer or string
:param serial: integer
:return: integer always increasing with revision numbers
"""
try:
releaselevel = int(releaselevel)
except ValueError:
releaselevel = RELEASE_LEVEL_VALUE.get(releaselevel, 0)
hex_version = int(serial)
hex_version |= releaselevel * 1 << 4
hex_version |= int(micro) * 1 << 8
hex_version |= int(minor) * 1 << 16
hex_version |= int(major) * 1 << 24
return hex_version
hexversion = calc_hexversion(*version_info)
if __name__ == "__main__":
print(version)
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2015-2016 European Synchrotron Radiation Facility
# Copyright (c) 2015-2018 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
......@@ -27,14 +27,19 @@ from __future__ import absolute_import, print_function, division
__authors__ = ["Jérôme Kieffer"]
__license__ = "MIT"
__date__ = "28/01/2016"
__date__ = "26/04/2018"
import os as _os
import logging as _logging
# Attach a do nothing logging handler for xsocs
_logging.getLogger(__name__).addHandler(_logging.NullHandler())
project = _os.path.basename(_os.path.dirname(_os.path.abspath(__file__)))
import os
project = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
try:
from ._version import __date__ as date # noqa
from ._version import version, version_info, hexversion, strictversion
# noqa
from ._version import version, version_info, hexversion, strictversion # noqa
except ImportError:
raise RuntimeError("Do NOT use %s from its sources: "
"build it and use the built version" % project)
raise RuntimeError("Do NOT use %s from its sources: build it and use the built version" % project)
......@@ -26,8 +26,10 @@
from __future__ import absolute_import
import sys
import logging
logging.basicConfig()
import sys
from silx.utils.launcher import Launcher
from xsocs import version
......
......@@ -26,6 +26,17 @@
from __future__ import absolute_import
import logging
_logger = logging.getLogger(__name__)
# Import PyQt5 before importing silx.gui
try:
import PyQt5.QtCore # Forces silx to use PyQt5
except ImportError as e:
_logger.error("Cannot load PyQt5: PyQt5 is required to run XSocs")
raise e
from silx.gui import colors as _colors
# Set colormaps available from silx colormap dialog
......
......@@ -29,6 +29,7 @@ __authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
import logging
import os
import time
import shutil
......@@ -46,6 +47,8 @@ from ..widgets.Buttons import FixedSizePushButon
from silx.gui import qt as Qt
_logger = logging.getLogger(__name__)
_HELP_WIDGET_STYLE = """
QLabel {
border-radius: 10px;
......@@ -881,14 +884,14 @@ class MergeWidget(Qt.QDialog):
assert_non_none(acqParamWid.beam_energy)
name = 'Direct beam'
dir_beam_h = assert_non_none(acqParamWid.direct_beam_h)
dir_beam_v = assert_non_none(acqParamWid.direct_beam_v)
merger.center_chan = [dir_beam_h, dir_beam_v]
dir_beam_h = assert_non_none(acqParamWid.direct_beam_h)
merger.center_chan = [dir_beam_v, dir_beam_h]
name = 'Channel per degree'
chpdeg_h = assert_non_none(acqParamWid.chperdeg_h)
chpdeg_v = assert_non_none(acqParamWid.chperdeg_v)
merger.chan_per_deg = [chpdeg_h, chpdeg_v]
chpdeg_h = assert_non_none(acqParamWid.chperdeg_h)
merger.chan_per_deg = [chpdeg_v, chpdeg_h]
name = 'Prefix'
prefix = self.__output['prefix']
......@@ -1159,8 +1162,27 @@ class MergeWidget(Qt.QDialog):
nNoMatch = len(no_match_ids)
nNoImg = len(no_img_ids)
# Retrieve calibration from selected ids
self.__scansGbx.setEnabled(enable)
self.__acqParamsGbx.setEnabled(len(matched_ids) > 0)
self.__acqParamsGbx.setEnabled(len(selected_ids) > 0)
# Set-up default values from first selected scan
if len(selected_ids) > 0:
calib = self.__merger.get_calibration(selected_ids[0])
if calib:
_logger.info("Load calibration information from scan %s",
selected_ids[0])
if 'mononrj' in calib:
# Convert from keV to eV
self.__acqParamWid.beam_energy = calib['mononrj'] * 1000
if 'pixperdeg' in calib:
self.__acqParamWid.chperdeg_v = calib['pixperdeg']
self.__acqParamWid.chperdeg_h = calib['pixperdeg']
if 'cen_pix_x' in calib:
self.__acqParamWid.direct_beam_h = calib['cen_pix_x']
if 'cen_pix_y' in calib:
self.__acqParamWid.direct_beam_v = calib['cen_pix_y']
self.__totalScansEdit.setText(str(nTotal))
self.__selectedScansEdit.setText(str(nSelected))
......
......@@ -84,10 +84,10 @@ class ConversionParamsWidget(Qt.QWidget):
self.__acqParamWid = AcqParamsWidget()
# Set default values with provided info
self.__acqParamWid.beam_energy = beamEnergy
self.__acqParamWid.direct_beam_h = directBeam[0]
self.__acqParamWid.direct_beam_v = directBeam[1]
self.__acqParamWid.chperdeg_h = channelsPerDegree[0]
self.__acqParamWid.chperdeg_v = channelsPerDegree[1]
self.__acqParamWid.direct_beam_v = directBeam[0]
self.__acqParamWid.direct_beam_h = directBeam[1]
self.__acqParamWid.chperdeg_v = channelsPerDegree[0]
self.__acqParamWid.chperdeg_h = channelsPerDegree[1]
grpLayout.addWidget(self.__acqParamWid)
self.layout().addWidget(acqParamsGbx)
......@@ -427,8 +427,8 @@ class ConversionParamsWidget(Qt.QWidget):
If one input is missing, it returns None.
"""
directBeam = (self.__acqParamWid.direct_beam_h,
self.__acqParamWid.direct_beam_v)
directBeam = (self.__acqParamWid.direct_beam_v,
self.__acqParamWid.direct_beam_h)
return None if None in directBeam else directBeam
def getChannelsPerDegree(self):
......@@ -436,8 +436,8 @@ class ConversionParamsWidget(Qt.QWidget):
If one input is missing, it returns None.
"""
channelsPerDegree = (self.__acqParamWid.chperdeg_h,
self.__acqParamWid.chperdeg_v)
channelsPerDegree = (self.__acqParamWid.chperdeg_v,
self.__acqParamWid.chperdeg_h)
return None if None in channelsPerDegree else channelsPerDegree
......
......@@ -73,8 +73,8 @@ class AcqParamsWidget(Qt.QWidget):
# ===========
row = 0
h_layout = Qt.QHBoxLayout()
beam_nrg_edit = dblLineEditWidget(8)
h_layout.addWidget(beam_nrg_edit)
self.__beam_nrg_edit = dblLineEditWidget(8)
h_layout.addWidget(self.__beam_nrg_edit)
h_layout.addWidget(Qt.QLabel('<b>eV</b>'))
layout.addWidget(Qt.QLabel('Beam energy :'), row, 0)
......@@ -96,12 +96,12 @@ class AcqParamsWidget(Qt.QWidget):
h_layout = Qt.QHBoxLayout()
layout.addLayout(h_layout, row, 1,
alignment=Qt.Qt.AlignLeft | Qt.Qt.AlignTop)
dir_beam_h_edit = dblLineEditWidget(6)
self.__dir_beam_v_edit = dblLineEditWidget(6)
h_layout.addWidget(Qt.QLabel('v='))
h_layout.addWidget(dir_beam_h_edit)
dir_beam_v_edit = dblLineEditWidget(6)
h_layout.addWidget(self.__dir_beam_v_edit)
self.__dir_beam_h_edit = dblLineEditWidget(6)
h_layout.addWidget(Qt.QLabel(' h='))
h_layout.addWidget(dir_beam_v_edit)
h_layout.addWidget(self.__dir_beam_h_edit)
h_layout.addWidget(Qt.QLabel('<b>px</b>'))
layout.addWidget(Qt.QLabel('Direct beam :'), row, 0)
......@@ -121,12 +121,12 @@ class AcqParamsWidget(Qt.QWidget):
h_layout = Qt.QHBoxLayout()