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