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 "IlluminaClipStep.h"
23
24 #include <QMainWindow>
25
26 #include <U2Core/AppContext.h>
27 #include <U2Core/BaseDocumentFormats.h>
28 #include <U2Core/QObjectScopedPointer.h>
29 #include <U2Core/U2SafePoints.h>
30
31 #include <U2Gui/DialogUtils.h>
32 #include <U2Gui/HelpButton.h>
33 #include <U2Gui/LastUsedDirHelper.h>
34 #include <U2Gui/MainWindow.h>
35 #include <U2Gui/U2FileDialog.h>
36
37 #include "trimmomatic/util/LineEditHighlighter.h"
38
39 namespace U2 {
40 namespace LocalWorkflow {
41
42 const QString IlluminaClipStepFactory::ID = "ILLUMINACLIP";
43
IlluminaClipStep()44 IlluminaClipStep::IlluminaClipStep()
45 : TrimmomaticStep(IlluminaClipStepFactory::ID) {
46 name = "ILLUMINACLIP";
47 description = tr("<html><head></head><body>"
48 "<h4>ILLUMINACLIP</h4>"
49 "<p>This step is used to find and remove Illumina adapters.</p>"
50 "<p>Trimmomatic first compares short sections of an adapter and a read. If they match enough, "
51 "the entire alignment between the read and adapter is scored. For paired-end reads, the \"palindrome\" "
52 "approach is also used to improve the result. See Trimmomatic manual for details.</p>"
53 "<p>Input the following values:</p>"
54 "<ul>"
55 "<li><b>Adapter sequences</b>: a FASTA file with the adapter sequences. Files for TruSeq2 "
56 "(GAII machines), TruSeq3 (HiSeq and MiSeq machines) and Nextera kits for SE and PE reads are "
57 "now available by default. The naming of the various sequences within the specified file "
58 "determines how they are used.</li>"
59 "<li><b>Seed mismatches</b>: the maximum mismatch count in short sections which will still allow "
60 "a full match to be performed.</li>"
61 "<li><b>Simple clip threshold</b>: a threshold for simple alignment mode. Values between 7 and 15 "
62 "are recommended. A perfect match of a 12 base sequence will score just over 7, while 25 bases "
63 "are needed to score 15.</li>"
64 "<li><b>Palindrome clip threshold</b>: a threshold for palindrome alignment mode. For palindromic "
65 "matches, a longer alignment is possible. Therefore the threshold can be in the range of 30. "
66 "Even though this threshold is very high (requiring a match of almost 50 bases) Trimmomatic is "
67 "still able to identify very, very short adapter fragments.</li>"
68 "</ul>"
69 "<p>There are also two optional parameters for palindrome mode: <b>Min adapter length</b> and "
70 "<b>Keep both reads</b>."
71 "</body></html>");
72 }
73
createWidget() const74 TrimmomaticStepSettingsWidget *IlluminaClipStep::createWidget() const {
75 return new IlluminaClipSettingsWidget();
76 }
77
serializeState(const QVariantMap & widgetState) const78 QString IlluminaClipStep::serializeState(const QVariantMap &widgetState) const {
79 QString serializedState;
80 serializedState += "\'" + widgetState.value(IlluminaClipSettingsWidget::FASTA_WITH_ADAPTERS_ETC, "").toString() + "\'";
81
82 serializedState += ":";
83
84 if (widgetState.contains(IlluminaClipSettingsWidget::SEED_MISMATCHES)) {
85 serializedState += QString::number(widgetState.value(IlluminaClipSettingsWidget::SEED_MISMATCHES).toInt());
86 }
87
88 serializedState += ":";
89
90 if (widgetState.contains(IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD)) {
91 serializedState += QString::number(widgetState.value(IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD).toInt());
92 }
93
94 serializedState += ":";
95
96 if (widgetState.contains(IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD)) {
97 serializedState += QString::number(widgetState.value(IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD).toInt());
98 }
99
100 if (widgetState.value(IlluminaClipAdditionalSettingsDialog::ADDITIONAL_SETTINGS_ENABLED, false).toBool()) {
101 serializedState += ":";
102
103 if (widgetState.contains(IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH)) {
104 serializedState += QString::number(widgetState.value(IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH).toInt());
105 }
106
107 serializedState += ":";
108
109 if (widgetState.contains(IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS)) {
110 serializedState += widgetState.value(IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS).toBool() ? "true" : "false";
111 }
112 }
113
114 return serializedState;
115 }
116
parseState(const QString & command) const117 QVariantMap IlluminaClipStep::parseState(const QString &command) const {
118 QVariantMap state;
119 QRegExp regExp(id + ":" + "\\\'([^\\\']*)\\'" + ":" + "(\\d*)" + ":" + "(\\d*)" + ":" + "(\\d*)" +
120 "(:" + "(\\d*)" + ":" + "((true|false){0,1})" + ")?",
121 Qt::CaseInsensitive);
122
123 const bool matched = regExp.exactMatch(command);
124 CHECK(matched, state);
125
126 const QString fastaWithAdaptersEtc = regExp.cap(1);
127 if (!fastaWithAdaptersEtc.isEmpty()) {
128 state[IlluminaClipSettingsWidget::FASTA_WITH_ADAPTERS_ETC] = fastaWithAdaptersEtc;
129 }
130
131 const QString seedMismatches = regExp.cap(2);
132 if (!seedMismatches.isEmpty()) {
133 state[IlluminaClipSettingsWidget::SEED_MISMATCHES] = seedMismatches.toInt();
134 }
135
136 const QString palindromeClipThreshold = regExp.cap(3);
137 if (!palindromeClipThreshold.isEmpty()) {
138 state[IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD] = palindromeClipThreshold.toInt();
139 }
140
141 const QString simpleClipThreshold = regExp.cap(4);
142 if (!simpleClipThreshold.isEmpty()) {
143 state[IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD] = simpleClipThreshold.toInt();
144 }
145
146 if (!regExp.cap(5).isEmpty()) {
147 state[IlluminaClipAdditionalSettingsDialog::ADDITIONAL_SETTINGS_ENABLED] = true;
148
149 const QString minAdapterLength = regExp.cap(6);
150 if (!minAdapterLength.isEmpty()) {
151 state[IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH] = minAdapterLength.toInt();
152 }
153
154 const QString keepBothReads = regExp.cap(7);
155 if (!keepBothReads.isEmpty()) {
156 state[IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS] = (keepBothReads.compare("true", Qt::CaseInsensitive) == 0);
157 }
158 }
159
160 return state;
161 }
162
163 const QString IlluminaClipSettingsWidget::FASTA_WITH_ADAPTERS_ETC = "fastaWithAdaptersEtc";
164 const QString IlluminaClipSettingsWidget::SEED_MISMATCHES = "seedMismatches";
165 const QString IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD = "palindromeClipThreshold";
166 const QString IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD = "simpleClipThreshold";
167
168 const QString IlluminaClipSettingsWidget::DEFAULT_SE_ADAPTERS = "TruSeq3-SE.fa";
169 const QString IlluminaClipSettingsWidget::DEFAULT_PE_ADAPTERS = "TruSeq3-PE-2.fa";
170
IlluminaClipSettingsWidget()171 IlluminaClipSettingsWidget::IlluminaClipSettingsWidget() {
172 setupUi(this);
173
174 fileName->setText(QDir::toNativeSeparators(QDir("data:").path() + "/adapters/illumina/" + DEFAULT_SE_ADAPTERS)); // The default adapters should be set depending on another attribute value
175 new LineEditHighlighter(fileName);
176
177 connect(fileName, SIGNAL(textChanged(QString)), SIGNAL(si_valueChanged()));
178 connect(mismatches, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged()));
179 connect(palindromeThreshold, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged()));
180 connect(simpleThreshold, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged()));
181 connect(tbBrowse, SIGNAL(clicked(bool)), SLOT(sl_browseButtonClicked()));
182 connect(pushButton, SIGNAL(clicked(bool)), SLOT(sl_optionalButtonClicked()));
183 }
184
~IlluminaClipSettingsWidget()185 IlluminaClipSettingsWidget::~IlluminaClipSettingsWidget() {
186 emit si_widgetIsAboutToBeDestroyed(getState());
187 }
188
validate() const189 bool IlluminaClipSettingsWidget::validate() const {
190 return !fileName->text().isEmpty();
191 }
192
getState() const193 QVariantMap IlluminaClipSettingsWidget::getState() const {
194 QVariantMap state;
195
196 const QString fastaWithAdaptersEtc = fileName->text();
197 if (!fastaWithAdaptersEtc.isEmpty()) {
198 state[FASTA_WITH_ADAPTERS_ETC] = fastaWithAdaptersEtc;
199 }
200
201 state[SEED_MISMATCHES] = mismatches->value();
202 state[PALINDROME_CLIP_THRESHOLD] = palindromeThreshold->value();
203 state[SIMPLE_CLIP_THRESHOLD] = simpleThreshold->value();
204
205 return state.unite(additionalOptions);
206 }
207
setState(const QVariantMap & state)208 void IlluminaClipSettingsWidget::setState(const QVariantMap &state) {
209 bool contains = state.contains(FASTA_WITH_ADAPTERS_ETC);
210 if (contains) {
211 fileName->setText(state[FASTA_WITH_ADAPTERS_ETC].toString());
212 }
213
214 contains = state.contains(SEED_MISMATCHES);
215 bool valid = false;
216 const int seedMismatches = state[SEED_MISMATCHES].toInt(&valid);
217 if (contains && valid) {
218 mismatches->setValue(seedMismatches);
219 }
220
221 contains = state.contains(PALINDROME_CLIP_THRESHOLD);
222 const int palindromeClipThreshold = state[PALINDROME_CLIP_THRESHOLD].toInt(&valid);
223 if (contains && valid) {
224 palindromeThreshold->setValue(palindromeClipThreshold);
225 }
226
227 contains = state.contains(SIMPLE_CLIP_THRESHOLD);
228 const int simpleClipThreshold = state[SIMPLE_CLIP_THRESHOLD].toInt(&valid);
229 if (contains && valid) {
230 simpleThreshold->setValue(simpleClipThreshold);
231 }
232
233 additionalOptions = IlluminaClipAdditionalSettingsDialog::extractState(state);
234 }
235
sl_browseButtonClicked()236 void IlluminaClipSettingsWidget::sl_browseButtonClicked() {
237 QString defaultDir = QDir::searchPaths(PATH_PREFIX_DATA).first() + "/adapters/illumina";
238 LastUsedDirHelper dirHelper("trimmomatic/adapters", defaultDir);
239
240 const QString filter = DialogUtils::prepareDocumentsFileFilter(BaseDocumentFormats::FASTA, true, QStringList());
241 QString defaultFilter = DialogUtils::prepareDocumentsFileFilter(BaseDocumentFormats::FASTA, false);
242 const QString adaptersFilePath = U2FileDialog::getOpenFileName(this, tr("Open FASTA with adapters"), dirHelper.dir, filter, &defaultFilter);
243 if (!adaptersFilePath.isEmpty()) {
244 dirHelper.url = adaptersFilePath;
245 fileName->setText(adaptersFilePath);
246 }
247 }
248
sl_optionalButtonClicked()249 void IlluminaClipSettingsWidget::sl_optionalButtonClicked() {
250 QObjectScopedPointer<IlluminaClipAdditionalSettingsDialog> additionalOptionsDialog(new IlluminaClipAdditionalSettingsDialog(additionalOptions, AppContext::getMainWindow()->getQMainWindow()));
251 const int executionResult = additionalOptionsDialog->exec();
252 if (static_cast<QDialog::DialogCode>(executionResult) == QDialog::Accepted) {
253 CHECK(!additionalOptionsDialog.isNull(), );
254 additionalOptions = additionalOptionsDialog->getState();
255 }
256 }
257
258 const QString IlluminaClipAdditionalSettingsDialog::ADDITIONAL_SETTINGS_ENABLED = "additionalSettingsEnabled";
259 const QString IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH = "minAdapterLength";
260 const QString IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS = "keepBothReads";
261
IlluminaClipAdditionalSettingsDialog(const QVariantMap & widgetState,QWidget * parent)262 IlluminaClipAdditionalSettingsDialog::IlluminaClipAdditionalSettingsDialog(const QVariantMap &widgetState, QWidget *parent)
263 : QDialog(parent) {
264 setupUi(this);
265
266 new HelpButton(this, buttonBox, "65930159");
267
268 buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Apply"));
269
270 keepBothCombo->addItem(tr("True"), true);
271 keepBothCombo->addItem(tr("False"), false);
272
273 groupBox->setChecked(widgetState.value(ADDITIONAL_SETTINGS_ENABLED, false).toBool());
274 minLengthSpin->setValue(widgetState.value(MIN_ADAPTER_LENGTH, 8).toInt());
275 keepBothCombo->setCurrentIndex(keepBothCombo->findData(widgetState.value(KEEP_BOTH_READS, false).toBool()));
276 }
277
extractState(const QVariantMap & fromState)278 QVariantMap IlluminaClipAdditionalSettingsDialog::extractState(const QVariantMap &fromState) {
279 QVariantMap state;
280 state[ADDITIONAL_SETTINGS_ENABLED] = fromState.value(ADDITIONAL_SETTINGS_ENABLED, false);
281 state[MIN_ADAPTER_LENGTH] = fromState.value(MIN_ADAPTER_LENGTH, 8);
282 state[KEEP_BOTH_READS] = fromState.value(KEEP_BOTH_READS, false);
283 return state;
284 }
285
getState() const286 QVariantMap IlluminaClipAdditionalSettingsDialog::getState() const {
287 QVariantMap state;
288 state[ADDITIONAL_SETTINGS_ENABLED] = groupBox->isChecked();
289 state[MIN_ADAPTER_LENGTH] = minLengthSpin->value();
290 state[KEEP_BOTH_READS] = keepBothCombo->currentData();
291 return state;
292 }
293
IlluminaClipStepFactory()294 IlluminaClipStepFactory::IlluminaClipStepFactory()
295 : TrimmomaticStepFactory(ID) {
296 }
297
createStep() const298 IlluminaClipStep *IlluminaClipStepFactory::createStep() const {
299 return new IlluminaClipStep();
300 }
301
302 } // namespace LocalWorkflow
303 } // namespace U2
304