Merge pull request #62 from PallasNeko/fix_safecrop

fix and move
This commit is contained in:
Prohurtz 2023-02-05 17:13:39 -06:00 committed by GitHub
commit 2b48be16db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 486 additions and 1120 deletions

View File

@ -19,8 +19,8 @@
@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@( @@@@@@@@@@@@@(
HSR By: Sean.Denka (Optimization Wizard, Contributor), Summer#2406 (Main Algorithm Engineer) HSR By: PallasNeko (Optimization Wizard, Contributor), Summer#2406 (Main Algorithm Engineer)
RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), Sean.Denka (Optimization) RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), PallasNeko (Optimization)
BLOB By: Prohurtz#0001 (Main App Developer) BLOB By: Prohurtz#0001 (Main App Developer)
Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator) Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator)
@ -51,10 +51,10 @@ if sys.platform.startswith("win"):
import importlib import importlib
from osc_calibrate_filter import * from osc_calibrate_filter import *
from haar_surround_feature import * from haar_surround_feature import External_Run_HSF
from blob import * from blob import *
from ransac import * from ransac import *
from hsrac import * from hsrac import External_Run_HSRACS
from blink import * from blink import *
@ -147,7 +147,6 @@ class EyeProcessor:
self.cccs = False self.cccs = False
self.ts = 10 self.ts = 10
self.previous_rotation = self.config.rotation_angle self.previous_rotation = self.config.rotation_angle
self.calibration_frame_counter
self.camera_model = None self.camera_model = None
self.detector_3d = None self.detector_3d = None
@ -246,9 +245,9 @@ class EyeProcessor:
def HSRACM(self): def HSRACM(self):
cx, cy, thresh, gray_frame, uncropframe = External_Run_HSRACS.HSRACS(self) cx, cy, thresh, gray_frame, uncropframe = External_Run_HSRACS().run(self.current_image_gray)
self.current_image_gray = gray_frame self.current_image_gray = gray_frame
if self.prev_x == None: if self.prev_x is None:
self.prev_x = cx self.prev_x = cx
self.prev_y = cy self.prev_y = cy
#print(self.prev_x, self.prev_y, cx, cy) #print(self.prev_x, self.prev_y, cx, cy)
@ -268,7 +267,7 @@ class EyeProcessor:
# print("EYE MOVED TOO FAST") # print("EYE MOVED TOO FAST")
# self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, 0, 0, 0, False)) # self.output_images_and_update(thresh, EyeInformation(InformationOrigin.HSRAC, 0, 0, 0, False))
def HSFM(self): def HSFM(self):
cx, cy, frame = External_Run_HSF.HSFS(self) cx, cy, frame = External_Run_HSF().run(self.current_image_gray)
self.eyeopen = intense(cx, cy, self.current_image_gray) self.eyeopen = intense(cx, cy, self.current_image_gray)
out_x, out_y = cal_osc(self, cx, cy) out_x, out_y = cal_osc(self, cx, cy)
if cx == 0: if cx == 0:
@ -347,7 +346,7 @@ class EyeProcessor:
elif self.settings.gui_RANSAC3D and self.settings.gui_RANSAC3DP == 4: elif self.settings.gui_RANSAC3D and self.settings.gui_RANSAC3DP == 4:
self.fourthalgo = self.RANSAC3DM self.fourthalgo = self.RANSAC3DM
if self.settings.gui_HSRAC == True and self.settings.gui_HSRACP == 1: if self.settings.gui_HSRAC and self.settings.gui_HSRACP == 1:
self.firstalgo = self.HSRACM self.firstalgo = self.HSRACM
elif self.settings.gui_HSRAC and self.settings.gui_HSRACP == 2: elif self.settings.gui_HSRAC and self.settings.gui_HSRACP == 2:
self.secondalgo = self.HSRACM self.secondalgo = self.HSRACM

View File

@ -1,16 +1,18 @@
import functools
import math
import sys
import timeit import timeit
from functools import lru_cache from functools import lru_cache
import cv2 import cv2
import numpy as np import numpy as np
from utils.misc_utils import clamp
from utils.img_utils import safe_crop
# from line_profiler_pycharm import profile # from line_profiler_pycharm import profile
video_path = "ezgif.com-gif-maker.avi" video_path = "ezgif.com-gif-maker.avi"
imshow_enable = True imshow_enable = False
calc_print_enable = True calc_print_enable = True
save_video = False save_video = False
skip_autoradius = False skip_autoradius = False
@ -27,181 +29,6 @@ blink_init_frames = 60 * 3 # 60fps*3sec,Number of blink statistical frames
# step==(x,y) # step==(x,y)
default_step = (5, 5) # bigger the steps,lower the processing time! ofc acc also takes an impact default_step = (5, 5) # bigger the steps,lower the processing time! ofc acc also takes an impact
"""
Attention.
If using cv2.filter2D in this code, be careful with the kernel
https://stackoverflow.com/questions/39457468/convolution-without-any-padding-opencv-python
"""
def TimeitWrapper(*args, **kwargs):
"""
This decorator @TimeitWrapper() prints the function name and execution time in seconds.
:param args:
:param kwargs:
:return:
"""
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
start = timeit.default_timer()
results = function(*args, **kwargs)
end = timeit.default_timer()
print('{} execution time: {:.10f} s'.format(function.__name__, end - start))
return results
return wrapper
return decorator
class TimeitResult(object):
"""
from https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
Object returned by the timeit magic with info about the run.
Contains the following attributes :
loops: (int) number of loops done per measurement
repeat: (int) number of times the measurement has been repeated
best: (float) best execution time / number
all_runs: (list of float) execution time of each run (in s)
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = best
self.worst = worst
self.all_runs = all_runs
self._precision = precision
self.timings = [dt / self.loops for dt in all_runs]
@property
def average(self):
return math.fsum(self.timings) / len(self.timings)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean=format_time(self.average, self._precision),
std=format_time(self.stdev, self._precision),
best=format_time(self.best, self._precision),
worst=format_time(self.worst, self._precision),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<TimeitResult : ' + unic + u'>')
class FPSResult(object):
"""
base https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = 1 / best
self.worst = 1 / worst
self.all_runs = all_runs
self._precision = precision
self.fps = [1 / dt for dt in all_runs]
self.unit = "fps"
@property
def average(self):
return math.fsum(self.fps) / len(self.fps)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.fps]) / len(self.fps)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean="%.*g%s" % (self._precision, self.average, self.unit),
std="%.*g%s" % (self._precision, self.stdev, self.unit),
best="%.*g%s" % (self._precision, self.best, self.unit),
worst="%.*g%s" % (self._precision, self.worst, self.unit),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<FPSResult : ' + unic + u'>')
def format_time(timespan, precision=3):
"""
https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L1473
Formats the timespan in a human readable form
"""
if timespan >= 60.0:
# we have more than a minute, format that in a human readable form
# Idea from http://snipplr.com/view/5713/
parts = [("d", 60 * 60 * 24), ("h", 60 * 60), ("min", 60), ("s", 1)]
time = []
leftover = timespan
for suffix, length in parts:
value = int(leftover / length)
if value > 0:
leftover = leftover % length
time.append(u'%s%s' % (str(value), suffix))
if leftover < 1:
break
return " ".join(time)
# Unfortunately the unicode 'micro' symbol can cause problems in
# certain terminals.
# See bug: https://bugs.launchpad.net/ipython/+bug/348466
# Try to prevent crashes by being more secure than it needs to
# E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
units = [u"s", u"ms", u'us', "ns"] # the save value
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb5'.encode(sys.stdout.encoding)
units = [u"s", u"ms", u'\xb5s', "ns"]
except:
pass
scaling = [1, 1e3, 1e6, 1e9]
if timespan > 0.0:
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
else:
order = 3
return u"%.*g %s" % (precision, timespan * scaling[order], units[order])
class CvParameters: class CvParameters:
# It may be a little slower because a dict named "self" is read for each function call. # It may be a little slower because a dict named "self" is read for each function call.
def __init__(self, radius, step): def __init__(self, radius, step):
@ -419,7 +246,7 @@ def conv_int(frame_int, kernel, xy_step, padding, xy_steps_list):
return frame_conv, min_response, center return frame_conv, min_response, center
class Auto_Radius_Calc(object): class AutoRadiusCalc(object):
def __init__(self): def __init__(self):
self.response_list = [] self.response_list = []
self.radius_cand_list = [] self.radius_cand_list = []
@ -522,7 +349,7 @@ class Auto_Radius_Calc(object):
return None return None
class Blink_Detector(object): class BlinkDetector(object):
def __init__(self): def __init__(self):
self.response_list = [] self.response_list = []
self.response_max = None self.response_max = None
@ -665,7 +492,7 @@ class CenterCorrection(object):
return out_x, out_y return out_x, out_y
class HSRAC_cls(object): class HSF_cls(object):
def __init__(self): def __init__(self):
# I'd like to take into account things like print, end_time - start_time processing time, etc., but it's too much trouble. # I'd like to take into account things like print, end_time - start_time processing time, etc., but it's too much trouble.
@ -679,9 +506,9 @@ class HSRAC_cls(object):
self.cv_modeo = ["first_frame", "radius_adjust", "blink_adjust", "normal"] self.cv_modeo = ["first_frame", "radius_adjust", "blink_adjust", "normal"]
self.now_modeo = self.cv_modeo[0] self.now_modeo = self.cv_modeo[0]
self.auto_radius_calc = Auto_Radius_Calc() self.auto_radius_calc = AutoRadiusCalc()
self.blink_detector = Blink_Detector() self.blink_detector = BlinkDetector()
self.center_q1 = Blink_Detector() self.center_q1 = BlinkDetector()
self.center_correct = CenterCorrection() self.center_correct = CenterCorrection()
self.cap = None self.cap = None
@ -710,8 +537,12 @@ class HSRAC_cls(object):
def single_run(self): def single_run(self):
# Temporary implementation to run # Temporary implementation to run
## default_radius = 14 ## default_radius = 14
# cropbox=[] # debug code
frame = self.current_image_gray frame = self.current_image_gray
if self.now_modeo == self.cv_modeo[1]: if self.now_modeo == self.cv_modeo[1]:
# adjustment of radius # adjustment of radius
@ -756,7 +587,12 @@ class HSRAC_cls(object):
lower_y = center_y - radius lower_y = center_y - radius
# Crop the image using the calculated bounds # Crop the image using the calculated bounds
cropped_image = gray_frame[lower_y:upper_y, lower_x:upper_x]
cropped_image = safe_crop(gray_frame, lower_x, lower_y, upper_x, upper_y)
# cropbox = [clamp(val, 0, gray_frame.shape[i]) for i, val in
# zip([1, 0, 1, 0], [lower_x, lower_y, upper_x, upper_y])] # debug code
if self.now_modeo == self.cv_modeo[0] or self.now_modeo == self.cv_modeo[1]: if self.now_modeo == self.cv_modeo[0] or self.now_modeo == self.cv_modeo[1]:
# If mode is first_frame or radius_adjust, record current radius and response # If mode is first_frame or radius_adjust, record current radius and response
@ -770,7 +606,13 @@ class HSRAC_cls(object):
lower_x = center_x - self.center_correct.center_q1_radius lower_x = center_x - self.center_correct.center_q1_radius
upper_y = center_y + self.center_correct.center_q1_radius upper_y = center_y + self.center_correct.center_q1_radius
lower_y = center_y - self.center_correct.center_q1_radius lower_y = center_y - self.center_correct.center_q1_radius
self.center_q1.add_response(cv2.mean(gray_frame[lower_y:upper_y, lower_x:upper_x])[0])
self.center_q1.add_response(
cv2.mean(safe_crop(gray_frame, lower_x, lower_y, upper_x, upper_y,keepsize=False))[
0
]
)
else: else:
@ -792,9 +634,19 @@ class HSRAC_cls(object):
else: else:
# pass # pass
if not self.center_correct.setup_comp: if not self.center_correct.setup_comp:
self.center_correct.init_array(gray_frame.shape, self.center_q1.quartile_1, radius) self.center_correct.init_array(
gray_frame.shape, self.center_q1.quartile_1, radius
)
elif self.center_correct.frame_shape != gray_frame.shape:
"""The resolution should have changed and the statistics should have changed, so essentially the statistics
need to be reworked, but implementation will be postponed as viability is the highest priority. """
self.center_correct.init_array(
gray_frame.shape, self.center_q1.quartile_1, radius
)
center_x, center_y = self.center_correct.correction(gray_frame, center_x, center_y) center_x, center_y = self.center_correct.correction(
gray_frame, center_x, center_y
)
# Define the center point and radius # Define the center point and radius
center_xy = (center_x, center_y) center_xy = (center_x, center_y)
upper_x = center_x + radius upper_x = center_x + radius
@ -802,10 +654,16 @@ class HSRAC_cls(object):
upper_y = center_y + radius upper_y = center_y + radius
lower_y = center_y - radius lower_y = center_y - radius
# Crop the image using the calculated bounds # Crop the image using the calculated bounds
cropped_image = gray_frame[lower_y:upper_y, lower_x:upper_x] cropped_image = safe_crop(
gray_frame, lower_x, lower_y, upper_x, upper_y
)
# cropbox = [clamp(val, 0, gray_frame.shape[i]) for i, val in
# zip([1, 0, 1, 0], [lower_x, lower_y, upper_x, upper_y])] # debug code
# if imshow_enable or save_video: # if imshow_enable or save_video:
# cv2.circle(frame, (orig_x, orig_y), 6, (0, 0, 255), -1) # cv2.circle(frame, (orig_x, orig_y), 6, (0, 0, 255), -1)
# cv2.circle(frame, (center_x, center_y), 3, (255, 0, 0), -1) # cv2.circle(frame, (center_x, center_y), 3, (255, 0, 0), -1)
# If you want to update response_max. it may be more cost-effective to rewrite response_list in the following way # If you want to update response_max. it may be more cost-effective to rewrite response_list in the following way
# https://stackoverflow.com/questions/42771110/fastest-way-to-left-cycle-a-numpy-array-like-pop-push-for-a-queue # https://stackoverflow.com/questions/42771110/fastest-way-to-left-cycle-a-numpy-array-like-pop-push-for-a-queue
@ -838,19 +696,27 @@ class HSRAC_cls(object):
else: else:
self.now_modeo = self.cv_modeo[1] self.now_modeo = self.cv_modeo[1]
# debug code
# return center_x,center_y,cropbox,frame
return center_x, center_y, frame return center_x, center_y, frame
class External_Run_HSF: class External_Run_HSF(object):
def __init__(self):
self.algo = HSF_cls()
hsrac = HSRAC_cls() def run(self, current_image_gray):
self.algo.current_image_gray = current_image_gray
def HSFS(self): # debug code
External_Run_HSF.hsrac.current_image_gray = self.current_image_gray # center_x, center_y,cropbox, frame = self.algo.single_run()
center_x, center_y, frame = External_Run_HSF.hsrac.single_run() # return center_x, center_y,cropbox, frame
center_x, center_y, frame = self.algo.single_run()
return center_x, center_y, frame return center_x, center_y, frame
if __name__ == '__main__':
hsrac = HSRAC_cls()
hsrac.open_video(video_path) if __name__ == "__main__":
while hsrac.read_frame(): hsf = HSF_cls()
_ = hsrac.single_run() hsf.open_video(video_path)
while hsf.read_frame():
_ = hsf.single_run()

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@
@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@( @@@@@@@@@@@@@(
RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), Sean.Denka (Optimization) RANSAC 3D By: Summer#2406 (Main Algorithm Engineer), Pupil Labs (pye3d), PallasNeko (Optimization)
Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator) Algorithm App Implimentations By: Prohurtz#0001, qdot (Inital App Creator)
Copyright (c) 2022 EyeTrackVR <3 Copyright (c) 2022 EyeTrackVR <3

View File

View File

@ -0,0 +1,12 @@
import cv2
def safe_crop(img, x, y, x2, y2, keepsize=True):
# The order of the arguments can be reconsidered.
img_h, img_w = img.shape[:2]
outimg = img[max(0, y) : min(img_h, y2), max(0, x) : min(img_w, x2)].copy()
reqsize_x, reqsize_y = abs(x2 - x), abs(y2 - y)
if keepsize and outimg.shape[:2] != (reqsize_y, reqsize_x):
# If the size is different from the expected size (smaller by the amount that is out of range)
outimg = cv2.resize(outimg, (reqsize_x, reqsize_y))
return outimg

View File

@ -0,0 +1,2 @@
def clamp(x, low, high):
return max(low, min(x, high))

View File

@ -0,0 +1,171 @@
import functools
import math
import sys
import timeit
def TimeitWrapper(*args, **kwargs):
"""
This decorator @TimeitWrapper() prints the function name and execution time in seconds.
:param args:
:param kwargs:
:return:
"""
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
start = timeit.default_timer()
results = function(*args, **kwargs)
end = timeit.default_timer()
print('{} execution time: {:.10f} s'.format(function.__name__, end - start))
return results
return wrapper
return decorator
class TimeitResult(object):
"""
from https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
Object returned by the timeit magic with info about the run.
Contains the following attributes :
loops: (int) number of loops done per measurement
repeat: (int) number of times the measurement has been repeated
best: (float) best execution time / number
all_runs: (list of float) execution time of each run (in s)
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = best
self.worst = worst
self.all_runs = all_runs
self._precision = precision
self.timings = [dt / self.loops for dt in all_runs]
@property
def average(self):
return math.fsum(self.timings) / len(self.timings)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean=format_time(self.average, self._precision),
std=format_time(self.stdev, self._precision),
best=format_time(self.best, self._precision),
worst=format_time(self.worst, self._precision),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<TimeitResult : ' + unic + u'>')
class FPSResult(object):
"""
base https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L55
"""
def __init__(self, loops, repeat, best, worst, all_runs, precision):
self.loops = loops
self.repeat = repeat
self.best = 1 / best
self.worst = 1 / worst
self.all_runs = all_runs
self._precision = precision
self.fps = [1 / dt for dt in all_runs]
self.unit = "fps"
@property
def average(self):
return math.fsum(self.fps) / len(self.fps)
@property
def stdev(self):
mean = self.average
return (math.fsum([(x - mean) ** 2 for x in self.fps]) / len(self.fps)) ** 0.5
def __str__(self):
pm = '+-'
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb1'.encode(sys.stdout.encoding)
pm = u'\xb1'
except:
pass
return "min:{best} max:{worst} mean:{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
pm=pm,
runs=self.repeat,
loops=self.loops,
loop_plural="" if self.loops == 1 else "s",
run_plural="" if self.repeat == 1 else "s",
mean="%.*g%s" % (self._precision, self.average, self.unit),
std="%.*g%s" % (self._precision, self.stdev, self.unit),
best="%.*g%s" % (self._precision, self.best, self.unit),
worst="%.*g%s" % (self._precision, self.worst, self.unit),
)
def _repr_pretty_(self, p, cycle):
unic = self.__str__()
p.text(u'<FPSResult : ' + unic + u'>')
def format_time(timespan, precision=3):
"""
https://github.com/ipython/ipython/blob/339c0d510a1f3cb2158dd8c6e7f4ac89aa4c89d8/IPython/core/magics/execution.py#L1473
Formats the timespan in a human readable form
"""
if timespan >= 60.0:
# we have more than a minute, format that in a human readable form
# Idea from http://snipplr.com/view/5713/
parts = [("d", 60 * 60 * 24), ("h", 60 * 60), ("min", 60), ("s", 1)]
time = []
leftover = timespan
for suffix, length in parts:
value = int(leftover / length)
if value > 0:
leftover = leftover % length
time.append(u'%s%s' % (str(value), suffix))
if leftover < 1:
break
return " ".join(time)
# Unfortunately the unicode 'micro' symbol can cause problems in
# certain terminals.
# See bug: https://bugs.launchpad.net/ipython/+bug/348466
# Try to prevent crashes by being more secure than it needs to
# E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
units = [u"s", u"ms", u'us', "ns"] # the save value
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
try:
u'\xb5'.encode(sys.stdout.encoding)
units = [u"s", u"ms", u'\xb5s', "ns"]
except:
pass
scaling = [1, 1e3, 1e6, 1e9]
if timespan > 0.0:
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
else:
order = 3
return u"%.*g %s" % (precision, timespan * scaling[order], units[order])