1 /**
2 * UGENE - Integrated Bioinformatics Tools.
3 * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4 * http://ugene.net
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #include "HMMBuildDialogController.h"
23
24 #include <QMessageBox>
25 #include <QPushButton>
26
27 #include <U2Core/AppContext.h>
28 #include <U2Core/BaseDocumentFormats.h>
29 #include <U2Core/Counter.h>
30 #include <U2Core/DNAAlphabet.h>
31 #include <U2Core/DocumentModel.h>
32 #include <U2Core/IOAdapter.h>
33 #include <U2Core/IOAdapterUtils.h>
34 #include <U2Core/LoadDocumentTask.h>
35 #include <U2Core/MultipleSequenceAlignmentObject.h>
36 #include <U2Core/U2OpStatusUtils.h>
37
38 #include <U2Gui/DialogUtils.h>
39 #include <U2Gui/HelpButton.h>
40 #include <U2Gui/LastUsedDirHelper.h>
41 #include <U2Gui/SaveDocumentController.h>
42 #include <U2Gui/U2FileDialog.h>
43
44 #include "HMMIO.h"
45 #include "TaskLocalStorage.h"
46 #include "hmmer2/funcs.h"
47
48 namespace U2 {
HMMBuildDialogController(const QString & _pn,const MultipleSequenceAlignment & _ma,QWidget * p)49 HMMBuildDialogController::HMMBuildDialogController(const QString &_pn, const MultipleSequenceAlignment &_ma, QWidget *p)
50 : QDialog(p),
51 ma(_ma->getCopy()),
52 profileName(_pn),
53 saveController(nullptr) {
54 setupUi(this);
55 new HelpButton(this, buttonBox, "65930810");
56 buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Build"));
57 buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Close"));
58
59 if (!ma->isEmpty()) {
60 msaFileButton->setHidden(true);
61 msaFileEdit->setHidden(true);
62 msaFileLabel->setHidden(true);
63 }
64
65 initSaveController();
66
67 okButton = buttonBox->button(QDialogButtonBox::Ok);
68 cancelButton = buttonBox->button(QDialogButtonBox::Cancel);
69
70 connect(msaFileButton, SIGNAL(clicked()), SLOT(sl_msaFileClicked()));
71 connect(okButton, SIGNAL(clicked()), SLOT(sl_okClicked()));
72
73 task = nullptr;
74 }
75
sl_msaFileClicked()76 void HMMBuildDialogController::sl_msaFileClicked() {
77 LastUsedDirHelper lod;
78 lod.url = U2FileDialog::getOpenFileName(this, tr("Select file with alignment"), lod, DialogUtils::prepareDocumentsFileFilterByObjType(GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT, true));
79
80 if (lod.url.isEmpty()) {
81 return;
82 }
83
84 msaFileEdit->setText(QFileInfo(lod.url).absoluteFilePath());
85 }
86
sl_okClicked()87 void HMMBuildDialogController::sl_okClicked() {
88 if (task != nullptr) {
89 accept(); // go to background
90 return;
91 }
92
93 // prepare model
94 UHMMBuildSettings s;
95 s.name = profileName;
96 QString errMsg;
97
98 QString inFile = msaFileEdit->text();
99 if (ma->isEmpty() && (inFile.isEmpty() || !QFileInfo(inFile).exists())) {
100 errMsg = tr("Incorrect alignment file!");
101 msaFileEdit->setFocus();
102 }
103 QString outFile = saveController->getSaveFileName();
104 if (outFile.isEmpty() && errMsg.isEmpty()) {
105 errMsg = tr("Incorrect HMM file!");
106 resultFileEdit->setFocus();
107 }
108 if (expertGroup->isChecked() && errMsg.isEmpty()) {
109 s.name = nameEdit->text().trimmed();
110 s.strategy = P7_LS_CONFIG;
111 if (hmmfsButton->isChecked()) {
112 s.strategy = P7_FS_CONFIG;
113 } else if (hmmsButton->isChecked()) {
114 s.strategy = P7_BASE_CONFIG;
115 } else if (hmmswButton->isChecked()) {
116 s.strategy = P7_SW_CONFIG;
117 }
118 }
119
120 if (!errMsg.isEmpty()) {
121 QMessageBox::critical(this, tr("Error"), errMsg);
122 return;
123 }
124
125 task = ma->isEmpty() ? new HMMBuildToFileTask(inFile, outFile, s) : new HMMBuildToFileTask(ma, outFile, s);
126 task->setReportingEnabled(true);
127 connect(task, SIGNAL(si_stateChanged()), SLOT(sl_onStateChanged()));
128 connect(task, SIGNAL(si_progressChanged()), SLOT(sl_onProgressChanged()));
129 AppContext::getTaskScheduler()->registerTopLevelTask(task);
130 statusLabel->setText(tr("Starting build process"));
131
132 // update buttons
133 okButton->setText(tr("Hide"));
134 cancelButton->setText(tr("Cancel"));
135
136 // new default behavior: hide dialog and use taskview to track the progress and results
137 accept(); // go to background
138 }
139
reject()140 void HMMBuildDialogController::reject() {
141 if (task != nullptr) {
142 task->cancel();
143 }
144 QDialog::reject();
145 }
146
sl_onStateChanged()147 void HMMBuildDialogController::sl_onStateChanged() {
148 Task *t = qobject_cast<Task *>(sender());
149 assert(task != nullptr);
150 if (task != t || t->getState() != Task::State_Finished) {
151 return;
152 }
153 task->disconnect(this);
154 const TaskStateInfo &si = task->getStateInfo();
155 if (si.hasError()) {
156 statusLabel->setText(tr("HMM build finished with errors: %1").arg(si.getError()));
157 } else if (task->isCanceled()) {
158 statusLabel->setText(tr("HMM build canceled"));
159 } else {
160 statusLabel->setText(tr("HMM build finished successfuly!"));
161 }
162 okButton->setText(tr("Build"));
163 cancelButton->setText(tr("Close"));
164
165 AppContext::getTaskScheduler()->disconnect(this);
166 task = nullptr;
167 }
168
sl_onProgressChanged()169 void HMMBuildDialogController::sl_onProgressChanged() {
170 assert(task == sender());
171 statusLabel->setText(tr("Progress: %1%").arg(task->getProgress()));
172 }
173
initSaveController()174 void HMMBuildDialogController::initSaveController() {
175 SaveDocumentControllerConfig config;
176 config.defaultDomain = HMMIO::HMM_ID;
177 config.defaultFormatId = HMMIO::HMM_ID;
178 config.fileDialogButton = resultFileButton;
179 config.fileNameEdit = resultFileEdit;
180 config.parentWidget = this;
181 config.saveTitle = tr("Select file with HMM profile");
182
183 SaveDocumentController::SimpleFormatsInfo formats;
184 formats.addFormat(HMMIO::HMM_ID, tr("HMM models"), QStringList(HMMIO::HMM_EXT));
185
186 saveController = new SaveDocumentController(config, formats, this);
187 }
188
189 //////////////////////////////////////////////////////////////////////////
190 // HMMBuildTask
191
HMMBuildToFileTask(const QString & inFile,const QString & _outFile,const UHMMBuildSettings & s)192 HMMBuildToFileTask::HMMBuildToFileTask(const QString &inFile, const QString &_outFile, const UHMMBuildSettings &s)
193 : Task("", TaskFlag_ReportingIsSupported),
194 settings(s),
195 outFile(_outFile),
196 loadTask(nullptr),
197 buildTask(nullptr) {
198 setTaskName(tr("Build HMM profile '%1' -> '%2'").arg(QFileInfo(inFile).fileName()).arg(QFileInfo(outFile).fileName()));
199 setVerboseLogMode(true);
200
201 assert(!(inFile.isEmpty() || outFile.isEmpty()));
202 // todo: support all formats with alignments!
203
204 DocumentFormatConstraints c;
205 c.checkRawData = true;
206 c.supportedObjectTypes += GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT;
207 c.rawData = IOAdapterUtils::readFileHeader(inFile);
208 c.addFlagToExclude(DocumentFormatFlag_CannotBeCreated);
209 QList<DocumentFormatId> formats = AppContext::getDocumentFormatRegistry()->selectFormats(c);
210 if (formats.isEmpty()) {
211 stateInfo.setError(tr("Error reading alignment file"));
212 return;
213 }
214
215 DocumentFormatId alnFormat = formats.first();
216
217 loadTask = new LoadDocumentTask(alnFormat, inFile, AppContext::getIOAdapterRegistry()->getIOAdapterFactoryById(IOAdapterUtils::url2io(inFile)), QVariantMap());
218 addSubTask(loadTask);
219 }
220
HMMBuildToFileTask(const MultipleSequenceAlignment & _ma,const QString & _outFile,const UHMMBuildSettings & s)221 HMMBuildToFileTask::HMMBuildToFileTask(const MultipleSequenceAlignment &_ma, const QString &_outFile, const UHMMBuildSettings &s)
222 : Task("", TaskFlags_FOSCOE | TaskFlag_ReportingIsSupported),
223 settings(s), outFile(_outFile), ma(_ma->getCopy()), loadTask(nullptr), buildTask(nullptr) {
224 setTaskName(tr("Build HMM profile to '%1'").arg(QFileInfo(outFile).fileName()));
225 setVerboseLogMode(true);
226
227 assert(!(outFile.isEmpty() || ma->isEmpty()));
228 if (settings.name.isEmpty()) {
229 QFileInfo fi(outFile);
230 settings.name = fi.baseName(); // FIXME temporary workaround
231 }
232 buildTask = new HMMBuildTask(settings, ma);
233 addSubTask(buildTask);
234 }
235
onSubTaskFinished(Task * subTask)236 QList<Task *> HMMBuildToFileTask::onSubTaskFinished(Task *subTask) {
237 QList<Task *> res;
238
239 if (hasError() || isCanceled()) {
240 return res;
241 }
242
243 if (subTask == loadTask) {
244 assert(ma->isEmpty());
245 Document *doc = loadTask->getDocument();
246 // assert(doc);
247 if (!doc) {
248 stateInfo.setError(tr("Incorrect input file"));
249 return res;
250 }
251 QList<GObject *> list = doc->findGObjectByType(GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT);
252 if (list.isEmpty()) {
253 stateInfo.setError(tr("Alignment object not found!"));
254 } else {
255 MultipleSequenceAlignmentObject *msa = qobject_cast<MultipleSequenceAlignmentObject *>(list.first());
256 const MultipleSequenceAlignment ma = msa->getMultipleAlignment();
257 if (settings.name.isEmpty()) {
258 settings.name = msa->getGObjectName() == MA_OBJECT_NAME ? doc->getName() : msa->getGObjectName();
259 }
260 buildTask = new HMMBuildTask(settings, ma);
261 res.append(buildTask);
262 }
263 }
264 return res;
265 }
266
run()267 void HMMBuildToFileTask::run() {
268 TaskLocalData::createHMMContext(getTaskId(), true);
269 _run();
270 TaskLocalData::freeHMMContext(getTaskId());
271 }
272
_run()273 void HMMBuildToFileTask::_run() {
274 if (stateInfo.hasError()) {
275 return;
276 }
277 assert(buildTask != nullptr);
278 assert(buildTask->getState() == Task::State_Finished);
279 if (buildTask->getStateInfo().hasError()) {
280 stateInfo.setError(buildTask->getStateInfo().getError());
281 return;
282 }
283 plan7_s *hmm = buildTask->getHMM();
284 assert(hmm != nullptr);
285 IOAdapterFactory *iof;
286 iof = AppContext::getIOAdapterRegistry()->getIOAdapterFactoryById(IOAdapterUtils::url2io(outFile));
287
288 // todo: write command line too!
289 HMMIO::writeHMM2(iof, outFile, stateInfo, hmm);
290 }
291
generateReport() const292 QString HMMBuildToFileTask::generateReport() const {
293 QString res;
294 res += "<table>";
295 res += "<tr><td width=200><b>" + tr("Source alignment") + "</b></td><td>" + (loadTask == nullptr ? settings.name : loadTask->getURL().getURLString()) + "</td></tr>";
296 res += "<tr><td><b>" + tr("Profile name") + "</b></td><td>" + settings.name + "</td></tr>";
297 if (hasError()) {
298 res += "<tr><td width=200><b>" + tr("Task was not finished") + "</b></td><td></td></tr>";
299 res += "</table>";
300 return res;
301 }
302
303 res += "<tr><td><b>" + tr("Profile file") + "</b></td><td>" + outFile + "</td></tr>";
304 res += "<tr><td><b>" + tr("Expert options") + "</b></td><td>";
305 switch (settings.strategy) {
306 case P7_BASE_CONFIG:
307 res += "-g";
308 break;
309 case P7_LS_CONFIG:
310 res += tr("none");
311 break;
312 case P7_FS_CONFIG:
313 res += "-f";
314 break;
315 case P7_SW_CONFIG:
316 res += "-s";
317 break;
318 }
319 res += "</td></tr>";
320
321 res += "</table>";
322 return res;
323 }
324
HMMBuildTask(const UHMMBuildSettings & s,const MultipleSequenceAlignment & _ma)325 HMMBuildTask::HMMBuildTask(const UHMMBuildSettings &s, const MultipleSequenceAlignment &_ma)
326 : Task("", TaskFlag_None), ma(_ma->getCopy()), settings(s), hmm(nullptr) {
327 GCOUNTER(cvar, "HMMBuildTask");
328 setTaskName(tr("Build HMM profile '%1'").arg(s.name));
329 }
330
~HMMBuildTask()331 HMMBuildTask::~HMMBuildTask() {
332 if (hmm != nullptr) {
333 FreePlan7(hmm);
334 }
335 }
336
run()337 void HMMBuildTask::run() {
338 TaskLocalData::createHMMContext(getTaskId(), true);
339 _run();
340 TaskLocalData::freeHMMContext(getTaskId());
341 }
342
_run()343 void HMMBuildTask::_run() {
344 if (ma->getNumRows() == 0) {
345 stateInfo.setError(tr("Multiple alignment is empty"));
346 return;
347 }
348 if (ma->getLength() == 0) {
349 stateInfo.setError(tr("Multiple alignment is of 0 length"));
350 return;
351 }
352 // todo: check HMM for RAW alphabet!
353 if (ma->getAlphabet()->isRaw()) {
354 stateInfo.setError(tr("Invalid alphabet! Only amino and nucleic alphabets are supported"));
355 return;
356 }
357
358 // everything ok here: fill msa
359
360 msa_struct *msa = MSAAlloc(ma->getNumRows(), ma->getLength());
361 if (msa == nullptr) {
362 stateInfo.setError(tr("Error creating MSA structure"));
363 return;
364 }
365 U2OpStatus2Log os;
366 for (int i = 0; i < ma->getNumRows(); i++) {
367 const MultipleSequenceAlignmentRow row = ma->getMsaRow(i);
368 QByteArray seq = row->toByteArray(os, ma->getLength());
369 free(msa->aseq[i]);
370 msa->aseq[i] = sre_strdup(seq.constData(), seq.size());
371 QByteArray name = row->getName().toLatin1();
372 msa->sqname[i] = sre_strdup(name.constData(), name.size());
373 msa->wgt[i] = 1.0; // default weights
374 }
375
376 int atype = ma->getAlphabet()->isNucleic() ? hmmNUCLEIC : hmmAMINO;
377 try {
378 hmm = UHMMBuild::build(msa, atype, settings, stateInfo);
379 } catch (const HMMException &e) {
380 stateInfo.setError(e.error);
381 }
382
383 assert(hmm != nullptr || stateInfo.hasError());
384
385 MSAFree(msa);
386 }
387
388 } // namespace U2
389