PageRenderTime 42ms CodeModel.GetById 7ms RepoModel.GetById 1ms app.codeStats 0ms

/psychopy/visual/backends/gamma.py

https://gitlab.com/braintech/psychopy-brain
Python | 356 lines | 285 code | 23 blank | 48 comment | 41 complexity | 609a229ab6d568720a008fa076ebb70a MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Part of the PsychoPy library
  4. # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019 Open Science Tools Ltd.
  5. # Distributed under the terms of the GNU General Public License (GPL).
  6. # set the gamma LUT using platform-specific hardware calls
  7. from __future__ import absolute_import, division, print_function
  8. from builtins import map
  9. from builtins import range
  10. import numpy
  11. import sys
  12. import platform
  13. import ctypes
  14. import ctypes.util
  15. from psychopy import logging, prefs
  16. import os
  17. # import platform specific C++ libs for controlling gamma
  18. if sys.platform == 'win32':
  19. from ctypes import windll
  20. elif sys.platform == 'darwin':
  21. try:
  22. carbon = ctypes.CDLL(
  23. '/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics')
  24. except OSError:
  25. try:
  26. carbon = ctypes.CDLL(
  27. '/System/Library/Frameworks/Carbon.framework/Carbon')
  28. except OSError:
  29. carbon = ctypes.CDLL('/System/Library/Carbon.framework/Carbon')
  30. elif sys.platform.startswith('linux'):
  31. # we need XF86VidMode
  32. xf86vm = ctypes.CDLL(ctypes.util.find_library('Xxf86vm'))
  33. _TravisTesting = os.environ.get('TRAVIS') == 'true' # in Travis-CI testing
  34. # Handling what to do if gamma can't be set
  35. if prefs.general['gammaErrorPolicy'] == 'abort': # more clear to Builder users
  36. defaultGammaErrorPolicy = 'raise' # more clear to Python coders
  37. else:
  38. defaultGammaErrorPolicy = prefs.general['gammaErrorPolicy']
  39. problem_msg = 'The hardware look-up table function ({func:s}) failed. '
  40. raise_msg = (problem_msg +
  41. 'If you would like to proceed without look-up table '
  42. '(gamma) changes, you can change your `defaultGammaFailPolicy` in the '
  43. 'application preferences. For more information see\n'
  44. 'https://www.psychopy.org/troubleshooting.html#errors-with-getting-setting-the-gamma-ramp '
  45. )
  46. warn_msg = problem_msg + 'Proceeding without look-up table (gamma) changes.'
  47. def setGamma(screenID=None, newGamma=1.0, rampType=None, rampSize=None,
  48. driver=None, xDisplay=None, gammaErrorPolicy=None):
  49. """Sets gamma to a given value
  50. :param screenID: The screen ID in the Operating system
  51. :param newGamma: numeric or triplet (for independent RGB gamma vals)
  52. :param rampType: see :ref:`createLinearRamp` for possible ramp types
  53. :param rampSize: how large is the lookup table on your system?
  54. :param driver: string describing your gfx card driver (e.g. from pyglet)
  55. :param xDisplay: for linux only
  56. :param gammaErrorPolicy: whether you want to raise an error or warning
  57. :return:
  58. """
  59. if not gammaErrorPolicy:
  60. gammaErrorPolicy = defaultGammaErrorPolicy
  61. # make sure gamma is 3x1 array
  62. if type(newGamma) in [float, int]:
  63. newGamma = numpy.tile(newGamma, [3, 1])
  64. elif type(newGamma) in [list, tuple]:
  65. newGamma = numpy.array(newGamma)
  66. newGamma.shape = [3, 1]
  67. elif type(newGamma) is numpy.ndarray:
  68. newGamma.shape = [3, 1]
  69. # create LUT from gamma values
  70. newLUT = numpy.tile(
  71. createLinearRamp(rampType=rampType, rampSize=rampSize, driver=driver),
  72. (3, 1)
  73. )
  74. if numpy.all(newGamma == 1.0) == False:
  75. # correctly handles 1 or 3x1 gamma vals
  76. newLUT = newLUT**(1.0/numpy.array(newGamma))
  77. setGammaRamp(screenID, newLUT,
  78. xDisplay=xDisplay, gammaErrorPolicy=gammaErrorPolicy)
  79. def setGammaRamp(screenID, newRamp, nAttempts=3, xDisplay=None,
  80. gammaErrorPolicy=None):
  81. """Sets the hardware look-up table, using platform-specific functions.
  82. Ramp should be provided as 3xN array in range 0:1.0
  83. On windows the first attempt to set the ramp doesn't always work. The
  84. parameter nAttemps allows the user to determine how many attempts should
  85. be made before failing
  86. """
  87. if not gammaErrorPolicy:
  88. gammaErrorPolicy = defaultGammaErrorPolicy
  89. LUTlength = newRamp.shape[1]
  90. if newRamp.shape[0] != 3 and newRamp.shape[1] == 3:
  91. newRamp = numpy.ascontiguousarray(newRamp.transpose())
  92. if sys.platform == 'win32':
  93. newRamp = (numpy.around(255.0 * newRamp)).astype(numpy.uint16)
  94. # necessary, according to pyglet post from Martin Spacek
  95. newRamp.byteswap(True)
  96. for n in range(nAttempts):
  97. success = windll.gdi32.SetDeviceGammaRamp(
  98. screenID, newRamp.ctypes) # FB 504
  99. if success:
  100. break
  101. if not success:
  102. func = 'SetDeviceGammaRamp'
  103. if gammaErrorPolicy == 'raise':
  104. raise OSError(raise_msg.format(func=func))
  105. elif gammaErrorPolicy == 'warn':
  106. logging.warning(warn_msg.format(func=func))
  107. if sys.platform == 'darwin':
  108. newRamp = (newRamp).astype(numpy.float32)
  109. error = carbon.CGSetDisplayTransferByTable(
  110. screenID, LUTlength,
  111. newRamp[0, :].ctypes,
  112. newRamp[1, :].ctypes,
  113. newRamp[2, :].ctypes)
  114. if error:
  115. func = 'CGSetDisplayTransferByTable'
  116. if gammaErrorPolicy == 'raise':
  117. raise OSError(raise_msg.format(func=func))
  118. elif gammaErrorPolicy == 'warn':
  119. logging.warning(warn_msg.format(func=func))
  120. if sys.platform.startswith('linux') and not _TravisTesting:
  121. newRamp = (numpy.around(65535 * newRamp)).astype(numpy.uint16)
  122. success = xf86vm.XF86VidModeSetGammaRamp(
  123. xDisplay, screenID, LUTlength,
  124. newRamp[0, :].ctypes,
  125. newRamp[1, :].ctypes,
  126. newRamp[2, :].ctypes)
  127. if not success:
  128. func = 'XF86VidModeSetGammaRamp'
  129. if gammaErrorPolicy == 'raise':
  130. raise OSError(raise_msg.format(func=func))
  131. elif gammaErrorPolicy == 'warn':
  132. logging.warning(raise_msg.format(func=func))
  133. elif _TravisTesting:
  134. logging.warn("It looks like we're running in the Travis-CI testing "
  135. "environment. Hardware gamma table cannot be set")
  136. def getGammaRamp(screenID, xDisplay=None, gammaErrorPolicy=None):
  137. """Ramp will be returned as 3xN array in range 0:1
  138. screenID :
  139. ID of the given display as defined by the OS
  140. xDisplay :
  141. the identity (int?) of the X display
  142. gammaErrorPolicy : str
  143. 'raise' (ends the experiment) or 'warn' (logs a warning)
  144. """
  145. if not gammaErrorPolicy:
  146. gammaErrorPolicy = defaultGammaErrorPolicy
  147. rampSize = getGammaRampSize(screenID, xDisplay=xDisplay)
  148. if sys.platform == 'win32':
  149. # init R, G, and B ramps
  150. origramps = numpy.empty((3, rampSize), dtype=numpy.uint16)
  151. success = windll.gdi32.GetDeviceGammaRamp(
  152. screenID, origramps.ctypes) # FB 504
  153. if not success:
  154. func = 'GetDeviceGammaRamp'
  155. if gammaErrorPolicy == 'raise':
  156. raise OSError(raise_msg.format(func=func))
  157. elif gammaErrorPolicy == 'warn':
  158. logging.warning(warn_msg.format(func=func))
  159. else:
  160. origramps.byteswap(True) # back to 0:255
  161. origramps = origramps/255.0 # rescale to 0:1
  162. if sys.platform == 'darwin':
  163. # init R, G, and B ramps
  164. origramps = numpy.empty((3, rampSize), dtype=numpy.float32)
  165. n = numpy.empty([1], dtype=numpy.int)
  166. error = carbon.CGGetDisplayTransferByTable(
  167. screenID, rampSize,
  168. origramps[0, :].ctypes,
  169. origramps[1, :].ctypes,
  170. origramps[2, :].ctypes, n.ctypes)
  171. if error:
  172. func = 'CGSetDisplayTransferByTable'
  173. if gammaErrorPolicy == 'raise':
  174. raise OSError(raise_msg.format(func=func))
  175. elif gammaErrorPolicy == 'warn':
  176. logging.warning(warn_msg.format(func=func))
  177. if sys.platform.startswith('linux') and not _TravisTesting:
  178. origramps = numpy.empty((3, rampSize), dtype=numpy.uint16)
  179. success = xf86vm.XF86VidModeGetGammaRamp(
  180. xDisplay, screenID, rampSize,
  181. origramps[0, :].ctypes,
  182. origramps[1, :].ctypes,
  183. origramps[2, :].ctypes)
  184. if not success:
  185. func = 'XF86VidModeGetGammaRamp'
  186. if gammaErrorPolicy == 'raise':
  187. raise OSError(raise_msg.format(func=func))
  188. elif gammaErrorPolicy == 'warn':
  189. logging.warning(warn_msg.format(func=func))
  190. else:
  191. origramps = origramps/65535.0 # rescale to 0:1
  192. elif _TravisTesting:
  193. logging.warn("It looks like we're running in the Travis-CI testing "
  194. "environment. Hardware gamma table cannot be retrieved")
  195. origramps = None
  196. return origramps
  197. def createLinearRamp(rampType=None, rampSize=256, driver=None):
  198. """Generate the Nx3 values for a linear gamma ramp on the current platform.
  199. This uses heuristics about known graphics cards to guess the 'rampType' if
  200. none is explicitly given.
  201. Much of this work is ported from LoadIdentityClut.m, by Mario Kleiner
  202. for the psychtoolbox
  203. rampType 0 : an 8-bit CLUT ranging 0:1
  204. This is seems correct for most windows machines and older macOS systems
  205. Known to be used by:
  206. OSX 10.4.9 PPC with GeForceFX-5200
  207. rampType 1 : an 8-bit CLUT ranging (1/256.0):1
  208. For some reason a number of macs then had a CLUT that (erroneously?)
  209. started with 1/256 rather than 0. Known to be used by:
  210. OSX 10.4.9 with ATI Mobility Radeon X1600
  211. OSX 10.5.8 with ATI Radeon HD-2600
  212. maybe all ATI cards?
  213. rampType 2 : a 10-bit CLUT ranging 0:(1023/1024)
  214. A slightly odd 10-bit CLUT that doesn't quite finish on 1.0!
  215. Known to be used by:
  216. OSX 10.5.8 with Geforce-9200M (MacMini)
  217. OSX 10.5.8 with Geforce-8800
  218. rampType 3 : a nasty, bug-fixing 10bit CLUT for crumby macOS drivers
  219. Craziest of them all for Snow leopard. Like rampType 2, except that
  220. the upper half of the table has 1/256.0 removed?!!
  221. Known to be used by:
  222. OSX 10.6.0 with NVidia Geforce-9200M
  223. """
  224. def _versionTuple(v):
  225. # for proper sorting: _versionTuple('10.8') < _versionTuple('10.10')
  226. return tuple(map(int, v.split('.')))
  227. if rampType is None:
  228. # try to determine rampType from heuristics including sys info
  229. osxVer = platform.mac_ver()[0] # '' on non-Mac
  230. # OSX
  231. if osxVer:
  232. osxVerTuple = _versionTuple(osxVer)
  233. # driver provided
  234. if driver is not None:
  235. # nvidia
  236. if 'NVIDIA' in driver:
  237. # leopard nVidia cards don't finish at 1.0!
  238. if _versionTuple("10.5") < osxVerTuple < _versionTuple("10.6"):
  239. rampType = 2
  240. # snow leopard cards are plain crazy!
  241. elif _versionTuple("10.6") < osxVerTuple:
  242. rampType = 3
  243. else:
  244. rampType = 1
  245. # non-nvidia
  246. else: # is ATI or unknown manufacturer, default to (1:256)/256
  247. # this is certainly correct for radeon2600 on 10.5.8 and
  248. # radeonX1600 on 10.4.9
  249. rampType = 1
  250. # no driver info given
  251. else: # is ATI or unknown manufacturer, default to (1:256)/256
  252. # this is certainly correct for radeon2600 on 10.5.8 and
  253. # radeonX1600 on 10.4.9
  254. rampType = 1
  255. # win32 or linux
  256. else: # for win32 and linux this is sensible, not clear about Vista and Windows7
  257. rampType = 0
  258. if rampType == 0:
  259. ramp = numpy.linspace(0.0, 1.0, num=rampSize)
  260. elif rampType == 1:
  261. ramp = numpy.linspace(1/256.0, 1.0, num=256)
  262. elif rampType == 2:
  263. ramp = numpy.linspace(0, 1023.0/1024, num=1024)
  264. elif rampType == 3:
  265. ramp = numpy.linspace(0, 1023.0/1024, num=1024)
  266. ramp[512:] = ramp[512:] - 1/256.0
  267. logging.info('Using gamma ramp type: %i' % rampType)
  268. return ramp
  269. def getGammaRampSize(screenID, xDisplay=None, gammaErrorPolicy=None):
  270. """Returns the size of each channel of the gamma ramp."""
  271. if not gammaErrorPolicy:
  272. gammaErrorPolicy = defaultGammaErrorPolicy
  273. if sys.platform == 'win32':
  274. # windows documentation (for SetDeviceGammaRamp) seems to indicate that
  275. # the LUT size is always 256
  276. rampSize = 256
  277. elif sys.platform == 'darwin':
  278. rampSize = carbon.CGDisplayGammaTableCapacity(screenID)
  279. elif sys.platform.startswith('linux') and not _TravisTesting:
  280. rampSize = ctypes.c_int()
  281. success = xf86vm.XF86VidModeGetGammaRampSize(
  282. xDisplay,
  283. screenID,
  284. ctypes.byref(rampSize)
  285. )
  286. if not success:
  287. func = 'XF86VidModeGetGammaRampSize'
  288. if gammaErrorPolicy == 'raise':
  289. raise OSError(raise_msg.format(func=func))
  290. elif gammaErrorPolicy == 'warn':
  291. logging.warning(warn_msg.format(func=func))
  292. else:
  293. rampSize = rampSize.value
  294. else:
  295. assert _TravisTesting
  296. rampSize = 256
  297. if rampSize == 0:
  298. logging.warn(
  299. "The size of the gamma ramp was reported as 0. This can " +
  300. "mean that gamma settings have no effect. Proceeding with " +
  301. "a default gamma ramp size."
  302. )
  303. rampSize = 256
  304. return rampSize