Skip to content

Commit 07d17a0

Browse files
Rashad Kanavathnyalldawson
Rashad Kanavath
authored andcommittedMar 20, 2019
This commits fixes encoding issue on windows.
getWindowsCodePage is taken from Grass7Utils.py Instead of writing a cli_file at startup, provider now pass all required env_variables directly to subprocess.popen. This has known to cause issues when handling with windows path names. subprocess.Popen handles it correctly depending on platform Logging of output from otbalgorithm and updating progress bar is slightly updated. Algoirthm is now launched directly using otbApplicationLauncherCommandLine `encoding` (on windows) and env arguments passed to subprocess is logged in QgsMessageLog
1 parent f85d494 commit 07d17a0

File tree

3 files changed

+138
-158
lines changed

3 files changed

+138
-158
lines changed
 

‎python/plugins/processing/algs/otb/OtbAlgorithm.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@
5353

5454
from processing.core.parameters import getParameterFromString
5555
from processing.algs.otb.OtbChoiceWidget import OtbParameterChoice
56-
from processing.algs.otb import OtbUtils
57-
56+
from processing.algs.otb.OtbUtils import OtbUtils
5857

5958
class OtbAlgorithm(QgsProcessingAlgorithm):
6059

@@ -200,8 +199,8 @@ def preprocessParameters(self, parameters):
200199
return valid_params
201200

202201
def processAlgorithm(self, parameters, context, feedback):
203-
otb_cli_file = OtbUtils.cliPath()
204-
command = '"{}" {} {}'.format(otb_cli_file, self.name(), OtbUtils.appFolder())
202+
app_launcher_path = OtbUtils.getExecutableInPath(OtbUtils.otbFolder(), 'otbApplicationLauncherCommandLine')
203+
command = '"{}" {} {}'.format(app_launcher_path, self.name(), OtbUtils.appFolder())
205204
outputPixelType = None
206205
for k, v in parameters.items():
207206
# if value is None for a parameter we don't have any businees with this key
@@ -264,11 +263,6 @@ def processAlgorithm(self, parameters, context, feedback):
264263
else:
265264
command += ' -{} "{}"'.format(out.name(), filePath)
266265

267-
QgsMessageLog.logMessage(self.tr('cmd={}'.format(command)), self.tr('Processing'), Qgis.Info)
268-
if not os.path.exists(otb_cli_file) or not os.path.isfile(otb_cli_file):
269-
import errno
270-
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), otb_cli_file)
271-
272266
OtbUtils.executeOtb(command, feedback)
273267

274268
result = {}

‎python/plugins/processing/algs/otb/OtbAlgorithmProvider.py

Lines changed: 6 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,10 @@
3535
from qgis import utils
3636

3737
from processing.core.ProcessingConfig import ProcessingConfig, Setting
38-
from processing.algs.otb import OtbUtils
38+
from processing.algs.otb.OtbUtils import OtbUtils
3939
from processing.algs.otb.OtbSettings import OtbSettings
4040
from processing.algs.otb.OtbAlgorithm import OtbAlgorithm
4141

42-
43-
def otb_exe_file(f):
44-
if os.name == 'nt':
45-
return f + '.exe'
46-
else:
47-
return f
48-
49-
5042
class OtbAlgorithmProvider(QgsProcessingProvider):
5143

5244
def __init__(self):
@@ -153,63 +145,6 @@ def loadAlgorithms(self):
153145
self.addAlgorithm(a)
154146
self.algs = []
155147

156-
otb_folder = self.normalize_path(OtbUtils.otbFolder())
157-
otb_app_path_env = os.pathsep.join(self.appDirs(OtbUtils.appFolder()))
158-
gdal_data_dir = None
159-
geotiff_csv_dir = None
160-
otbcli_path = OtbUtils.cliPath()
161-
try:
162-
if os.name == 'nt':
163-
app_vargs = " %*"
164-
export_cmd = 'SET '
165-
first_line = ':: Setup environment for OTB package. Generated by QGIS plugin'
166-
otb_app_launcher = os.path.join(otb_folder, 'bin', 'otbApplicationLauncherCommandLine.exe')
167-
gdal_data_dir = os.path.join(otb_folder, 'share', 'data')
168-
geotiff_csv_dir = os.path.join(otb_folder, 'share', 'epsg_csv')
169-
else:
170-
app_vargs = " \"$@\""
171-
export_cmd = 'export '
172-
first_line = '#!/bin/sh'
173-
otb_app_launcher = os.path.join(otb_folder, 'bin', 'otbApplicationLauncherCommandLine')
174-
lines = None
175-
env_profile = os.path.join(otb_folder, 'otbenv.profile')
176-
if os.path.exists(env_profile):
177-
with open(env_profile) as f:
178-
lines = f.readlines()
179-
lines = [x.strip() for x in lines]
180-
for line in lines:
181-
if not line or line.startswith('#'):
182-
continue
183-
if 'GDAL_DATA=' in line:
184-
gdal_data_dir = line.split("GDAL_DATA=")[1]
185-
if 'GEOTIFF_CSV='in line:
186-
geotiff_csv_dir = line.split("GEOTIFF_CSV=")[1]
187-
with open(otbcli_path, 'w') as otb_cli_file:
188-
otb_cli_file.write(first_line + os.linesep)
189-
otb_cli_file.write(export_cmd + "LC_NUMERIC=C" + os.linesep)
190-
otb_cli_file.write(export_cmd + "GDAL_DRIVER_PATH=disable" + os.linesep)
191-
if gdal_data_dir:
192-
otb_cli_file.write(export_cmd + "GDAL_DATA=" + "\"" + gdal_data_dir + "\"" + os.linesep)
193-
if geotiff_csv_dir:
194-
otb_cli_file.write(export_cmd + "GEOTIFF_CSV=" + "\"" + geotiff_csv_dir + "\"" + os.linesep)
195-
if OtbUtils.loggerLevel():
196-
otb_cli_file.write(export_cmd + "OTB_LOGGER_LEVEL=" + OtbUtils.loggerLevel() + os.linesep)
197-
max_ram_hint = OtbUtils.maxRAMHint()
198-
if max_ram_hint and not int(max_ram_hint) == 128:
199-
otb_cli_file.write(export_cmd + "OTB_MAX_RAM_HINT=" + max_ram_hint + os.linesep)
200-
otb_cli_file.write(export_cmd + "OTB_APPLICATION_PATH=" + "\"" + otb_app_path_env + "\"" + os.linesep)
201-
otb_cli_file.write("\"" + otb_app_launcher + "\"" + app_vargs + os.linesep)
202-
203-
if not os.name == 'nt':
204-
os.chmod(otbcli_path, 0o744)
205-
except BaseException as e:
206-
import traceback
207-
os.remove(otbcli_path)
208-
errmsg = "Cannot write:" + otbcli_path + "\nError:\n" + traceback.format_exc()
209-
QgsMessageLog.logMessage(self.tr(errmsg), self.tr('Processing'), Qgis.Critical)
210-
raise e
211-
QgsMessageLog.logMessage(self.tr("Using otbcli: '{}'.".format(otbcli_path)), self.tr('Processing'), Qgis.Info)
212-
213148
def canBeActivated(self):
214149
if not self.isActive():
215150
return False
@@ -256,10 +191,8 @@ def validateAppFolders(self, v):
256191
dfile = os.path.join(descr_folder, app_name + '.txt')
257192
isValid = True
258193
if not os.path.exists(dfile):
259-
cmdlist = [os.path.join(
260-
folder, 'bin',
261-
otb_exe_file('otbQgisDescriptor')),
262-
app_name, app_dir, descr_folder + '/']
194+
cmdlist = [OtbUtils.getExecutableInPath(folder, 'otbQgisDescriptor'),
195+
app_name, app_dir, descr_folder + '/']
263196
commands = ' '.join(cmdlist)
264197
QgsMessageLog.logMessage(self.tr(commands), self.tr('Processing'), Qgis.Critical)
265198
OtbUtils.executeOtb(commands, feedback=None)
@@ -283,9 +216,10 @@ def validateOtbFolder(self, v):
283216
self.setActive(False)
284217
raise ValueError(self.tr("'{}' does not exist. OTB provider will be disabled".format(v)))
285218
path = self.normalize_path(v)
286-
if not os.path.exists(os.path.join(path, 'bin', otb_exe_file('otbApplicationLauncherCommandLine'))):
219+
app_launcher_path = OtbUtils.getExecutableInPath(path, 'otbApplicationLauncherCommandLine')
220+
if not os.path.exists(app_launcher_path):
287221
self.setActive(False)
288-
raise ValueError(self.tr("Cannot find '{}'. OTB will be disabled".format(os.path.join(v, 'bin', otb_exe_file('otbApplicationLauncherCommandLine')))))
222+
raise ValueError(self.tr("Cannot find '{}'. OTB will be disabled".format(app_launcher_path)))
289223

290224
def algsFile(self, d):
291225
return os.path.join(self.descrFolder(d), 'algs.txt')

‎python/plugins/processing/algs/otb/OtbUtils.py

Lines changed: 129 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -40,92 +40,144 @@
4040
from processing.algs.otb.OtbSettings import OtbSettings
4141

4242

43-
def cliPath():
44-
cli_ext = '.bat' if os.name == 'nt' else ''
45-
return os.path.normpath(os.path.join(QgsApplication.qgisSettingsDirPath(),
46-
'processing', 'qgis_otb_cli' + cli_ext))
43+
class OtbUtils:
44+
45+
@staticmethod
46+
def version():
47+
return ProcessingConfig.getSetting(OtbSettings.VERSION) or '0.0.0'
48+
49+
@staticmethod
50+
def loggerLevel():
51+
return ProcessingConfig.getSetting(OtbSettings.LOGGER_LEVEL) or 'INFO'
52+
53+
@staticmethod
54+
def maxRAMHint():
55+
return ProcessingConfig.getSetting(OtbSettings.MAX_RAM_HINT) or ''
56+
57+
@staticmethod
58+
def otbFolder():
59+
if ProcessingConfig.getSetting(OtbSettings.FOLDER):
60+
return os.path.normpath(os.sep.join(re.split(r'\\|/', ProcessingConfig.getSetting(OtbSettings.FOLDER))))
61+
else:
62+
return None
63+
64+
@staticmethod
65+
def appFolder():
66+
app_folder = ProcessingConfig.getSetting(OtbSettings.APP_FOLDER)
67+
if app_folder:
68+
return os.pathsep.join(app_folder.split(';'))
69+
else:
70+
return None
71+
72+
@staticmethod
73+
def srtmFolder():
74+
return ProcessingConfig.getSetting(OtbSettings.SRTM_FOLDER) or ''
75+
76+
@staticmethod
77+
def geoidFile():
78+
return ProcessingConfig.getSetting(OtbSettings.GEOID_FILE) or ''
79+
80+
@staticmethod
81+
def getExecutableInPath(path, exe):
82+
ext = '.exe' if os.name == 'nt' else ''
83+
return os.path.join(path, 'bin', exe + ext)
84+
85+
@staticmethod
86+
def getAuxiliaryDataDirectories():
87+
gdal_data_dir = None
88+
gtiff_csv_dir = None
89+
otb_folder = OtbUtils.otbFolder()
90+
if os.name == 'nt':
91+
gdal_data_dir = os.path.join(otb_folder, 'share', 'data')
92+
gtiff_csv_dir = os.path.join(otb_folder, 'share', 'epsg_csv')
93+
else:
94+
env_profile = os.path.join(otb_folder, 'otbenv.profile')
95+
try:
96+
if os.path.exists(env_profile):
97+
with open(env_profile) as f:
98+
lines = f.readlines()
99+
lines = [x.strip() for x in lines]
100+
for line in lines:
101+
if not line or line.startswith('#'):
102+
continue
103+
if 'GDAL_DATA=' in line:
104+
gdal_data_dir = line.split("GDAL_DATA=")[1]
105+
if 'GEOTIFF_CSV='in line:
106+
gtiff_csv_dir = line.split("GEOTIFF_CSV=")[1]
107+
except BaseException as exc:
108+
errmsg = "Cannot find gdal and geotiff data directory." + str(exc)
109+
QgsMessageLog.logMessage(errmsg, OtbUtils.tr('Processing'), Qgis.Info)
110+
pass
111+
112+
return gdal_data_dir, gtiff_csv_dir
113+
114+
@staticmethod
115+
def executeOtb(commands, feedback, addToLog=True):
116+
otb_env = {
117+
'LC_NUMERIC': 'C',
118+
'GDAL_DRIVER_PATH': 'disable'
119+
}
120+
gdal_data_dir, gtiff_csv_dir = OtbUtils.getAuxiliaryDataDirectories()
121+
if gdal_data_dir and os.path.exists(gdal_data_dir):
122+
otb_env['GDAL_DATA'] = gdal_data_dir
123+
if gtiff_csv_dir and os.path.exists(gtiff_csv_dir):
124+
otb_env['GEOTIFF_CSV'] = gtiff_csv_dir
125+
126+
otb_env['OTB_LOGGER_LEVEL'] = OtbUtils.loggerLevel()
127+
max_ram_hint = OtbUtils.maxRAMHint()
128+
if max_ram_hint and int(max_ram_hint) > 256:
129+
otb_env['OTB_MAX_RAM_HINT'] = max_ram_hint
130+
131+
kw = {}
132+
kw['env'] = otb_env
133+
if os.name == 'nt' and sys.version_info >= (3, 6):
134+
kw['encoding'] = "cp{}".format(OtbUtils.getWindowsCodePage())
135+
136+
QgsMessageLog.logMessage("{}".format(kw), OtbUtils.tr('Processing'), Qgis.Info)
137+
QgsMessageLog.logMessage("cmd={}".format(commands), OtbUtils.tr('Processing'), Qgis.Info)
138+
with subprocess.Popen(
139+
commands,
140+
shell=True,
141+
stdout=subprocess.PIPE,
142+
stdin=subprocess.DEVNULL,
143+
stderr=subprocess.STDOUT,
144+
universal_newlines=True,
145+
**kw
146+
) as proc:
47147

48-
49-
def version():
50-
return ProcessingConfig.getSetting(OtbSettings.VERSION) or '0.0.0'
51-
52-
53-
def loggerLevel():
54-
return ProcessingConfig.getSetting(OtbSettings.LOGGER_LEVEL) or 'INFO'
55-
56-
57-
def maxRAMHint():
58-
return ProcessingConfig.getSetting(OtbSettings.MAX_RAM_HINT) or ''
59-
60-
61-
def otbFolder():
62-
if ProcessingConfig.getSetting(OtbSettings.FOLDER):
63-
return os.path.normpath(os.sep.join(re.split(r'\\|/', ProcessingConfig.getSetting(OtbSettings.FOLDER))))
64-
else:
65-
return None
66-
67-
68-
def appFolder():
69-
app_folder = ProcessingConfig.getSetting(OtbSettings.APP_FOLDER)
70-
if app_folder:
71-
return os.pathsep.join(app_folder.split(';'))
72-
else:
73-
return None
74-
75-
76-
def srtmFolder():
77-
return ProcessingConfig.getSetting(OtbSettings.SRTM_FOLDER) or ''
78-
79-
80-
def geoidFile():
81-
return ProcessingConfig.getSetting(OtbSettings.GEOID_FILE) or ''
82-
83-
84-
def executeOtb(command, feedback, addToLog=True):
85-
loglines = []
86-
with subprocess.Popen(
87-
[command],
88-
shell=True,
89-
stdout=subprocess.PIPE,
90-
stdin=subprocess.DEVNULL,
91-
stderr=subprocess.STDOUT,
92-
universal_newlines=True
93-
) as proc:
94-
try:
95148
for line in iter(proc.stdout.readline, ''):
96149
line = line.strip()
97150
#'* ]' and ' ]' says its some progress update
98-
#print('line[-3:]',line[-3:])
99-
if line[-3:] == "* ]" or line[-3:] == " ]":
151+
if '% [' in line:
100152
part = line.split(':')[1]
101153
percent = part.split('%')[0]
102154
try:
103155
if int(percent) >= 100:
104-
loglines.append(line)
156+
feedback.pushConsoleInfo(line)
105157
feedback.setProgress(int(percent))
106158
except:
107159
pass
108160
else:
109-
loglines.append(line)
110-
except BaseException as e:
111-
loglines.append(str(e))
112-
pass
113-
114-
for logline in loglines:
115-
if feedback is None:
116-
QgsMessageLog.logMessage(logline, 'Processing', Qgis.Info)
117-
else:
118-
feedback.pushConsoleInfo(logline)
119-
120-
# for logline in loglines:
121-
# if 'INFO' in logline or 'FATAL' in logline:
122-
# if feedback is None:
123-
# QgsMessageLog.logMessage(logline, 'Processing', Qgis.Info)
124-
# else:
125-
# feedback.pushConsoleInfo(logline)
126-
127-
128-
def tr(string, context=''):
129-
if context == '':
130-
context = 'OtbUtils'
131-
return QCoreApplication.translate(context, string)
161+
162+
if feedback is None:
163+
QgsMessageLog.logMessage(line, OtbUtils.tr('Processing'), Qgis.Info)
164+
else:
165+
if any([l in line for l in ['(WARNING)', '(FATAL)', 'ERROR']]):
166+
feedback.reportError(line)
167+
else:
168+
feedback.pushConsoleInfo(line.strip())
169+
170+
@staticmethod
171+
def getWindowsCodePage():
172+
"""
173+
Determines MS-Windows CMD.exe shell codepage.
174+
Used into GRASS exec script under MS-Windows.
175+
"""
176+
from ctypes import cdll
177+
return str(cdll.kernel32.GetACP())
178+
179+
@staticmethod
180+
def tr(string, context=''):
181+
if context == '':
182+
context = 'OtbUtils'
183+
return QCoreApplication.translate(context, string)

0 commit comments

Comments
 (0)
Please sign in to comment.