tripal_feature.api.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <?php
  2. /**
  3. * @file
  4. * Provides an application programming interface (API) for working with features
  5. */
  6. /**
  7. * @defgroup tripal_feature_api Feature API
  8. * @ingroup tripal_api
  9. * @{
  10. * Provides an application programming interface (API) for working with features
  11. * @}
  12. */
  13. /**
  14. * Performs a reverse compliment of a nucleotide sequence
  15. *
  16. * @param $sequence
  17. * The nucelotide sequence
  18. *
  19. * @return
  20. * an upper-case reverse complemented sequence
  21. *
  22. * @ingroup tripal_feature_api
  23. */
  24. function tripal_reverse_compliment_sequence($sequence) {
  25. $seq = strtoupper($sequence);
  26. $seq = strrev($seq);
  27. $seq = str_replace("A", "t", $seq);
  28. $seq = str_replace("T", "a", $seq);
  29. $seq = str_replace("G", "c", $seq);
  30. $seq = str_replace("C", "g", $seq);
  31. $seq = str_replace("Y", "r", $seq);
  32. $seq = str_replace("R", "y", $seq);
  33. $seq = str_replace("W", "w", $seq);
  34. $seq = str_replace("S", "s", $seq);
  35. $seq = str_replace("K", "m", $seq);
  36. $seq = str_replace("M", "k", $seq);
  37. $seq = str_replace("D", "h", $seq);
  38. $seq = str_replace("V", "b", $seq);
  39. $seq = str_replace("H", "d", $seq);
  40. $seq = str_replace("B", "v", $seq);
  41. return strtoupper($seq);
  42. }
  43. /**
  44. * Retrieves the sequence for a feature.
  45. *
  46. * @param $feature
  47. * An associative array describing the feature. Valid keys include:
  48. * - feature_id: The feature_id of the feature for which the sequence will be retrieved
  49. * - name: The feature name. This will appear on the FASTA definition line
  50. * @param $options
  51. * An associative array of options. Valid keys include:
  52. * - width: Indicate the number of bases to use per line. A new line will be added
  53. * after the specified number of bases on each line.
  54. * - derive_from_parent: Set to '1' if the sequence should be obtained from the parent
  55. * to which this feature is aligned.
  56. * - aggregate: Set to '1' if the sequence should only contain sub features, excluding
  57. * intro sub feature sequence. For example, set this option to obtain just
  58. * the coding sequence of an mRNA.
  59. * - output_format: The type of format. Valid formats include 'fasta_html', 'fasta_txt' and
  60. * 'raw'. The format 'fasta_txt' outputs line breaks as <br> tags and the entire
  61. * return value is in a <span> tag with a fixed-width font definition. 'fasta_txt'
  62. * outputs line breaks with windows format carriage returns (e.g. \r\n) with no other
  63. * formatting. The raw format is simply the sequence with now FASTA formatting and no
  64. * line breaks.
  65. * - num_upstream: An integer specifing the number of upstream bases to include in the output
  66. * - num_downstream: An integer specifying the number of downstream bases to include in the
  67. * output.
  68. * - sub_feature_types: Only include sub features (or child features) of the types
  69. * provided in the array
  70. * - relationship_type: If a relationship name is provided (e.g. sequence_of) then any
  71. * sequences that are in relationships of this type with matched sequences are also included
  72. * - relationship_part: If a relationship is provided in the preceeding argument then
  73. * the rel_part must be either 'object' or 'subject' to indicate which side of the
  74. * relationship the matched features belong
  75. *
  76. * @return
  77. * The DNA/protein sequence formated as requested.
  78. *
  79. * @ingroup tripal_feature_api
  80. */
  81. function tripal_format_sequence($feature, $options) {
  82. // Default Values
  83. $feature_id = $feature['feature_id'];
  84. $feature_name = $feature['name'];
  85. $num_bases_per_line = $options['width'];
  86. $derive_from_parent = $options['derive_from_parent'];
  87. $aggregate = $options['aggregate'];
  88. $output_format = $options['output_format'];
  89. $upstream = $options['num_upstream'];
  90. $downstream = $options['num_downstream'];
  91. $sub_features = $options['sub_feature_types'];
  92. $relationship = $options['relationship_type'];
  93. $rel_part = $options['relationship_part'];
  94. // to speed things up we need to make sure we have a persistent connection
  95. $connection = tripal_db_persistent_chado();
  96. if (!$upstream) {
  97. $upstream = 0;
  98. }
  99. if (!$downstream) {
  100. $downstream = 0;
  101. }
  102. if ($rel_part == "object" or $rel_part == "subject") {
  103. if ($rel_part == "subject") {
  104. $psql = '
  105. PREPARE feature_rel_get_object (int, text) AS
  106. SELECT FO.feature_id, FO.name, FO.uniquename, CVTO.name as feature_type, O.genus, O.species
  107. FROM feature FS
  108. INNER JOIN feature_relationship FR ON FR.subject_id = FS.feature_id
  109. INNER JOIN cvterm CVTFR ON CVTFR.cvterm_id = FR.type_id
  110. INNER JOIN feature FO ON FO.feature_id = FR.object_id
  111. INNER JOIN cvterm CVTO ON CVTO.cvterm_id = FO.type_id
  112. INNER JOIN organism O ON O.organism_id = FO.organism_id
  113. WHERE
  114. FS.feature_id = $1 AND
  115. CVTFR.name = $2
  116. ';
  117. $status = tripal_core_chado_prepare('feature_rel_get_object', $psql, array('int', 'text'));
  118. if (!$status) {
  119. tripal_report_error('tripal_feature', TRIPAL_ERROR, "init: not able to prepare SQL statement '%name'",
  120. array('%name' => 'feature_by_subject'));
  121. }
  122. $sql = "EXECUTE feature_rel_get_object(:feature_id, :relationship)";
  123. $features = chado_query($sql, array(':feature_id' => $feature_id, ':relationship' => $relationship));
  124. }
  125. if ($rel_part == "object") {
  126. $psql = '
  127. PREPARE feature_rel_get_subject (int, text) AS
  128. SELECT FS.feature_id, FS.name, FS.uniquename, CVTO.name as feature_type, O.genus, O.species
  129. FROM feature FO
  130. INNER JOIN feature_relationship FR ON FR.object_id = FO.feature_id
  131. INNER JOIN cvterm CVTFR ON CVTFR.cvterm_id = FR.type_id
  132. INNER JOIN feature FS ON FS.feature_id = FR.subject_id
  133. INNER JOIN cvterm CVTO ON CVTO.cvterm_id = FS.type_id
  134. INNER JOIN organism O ON O.organism_id = FS.organism_id
  135. WHERE
  136. FO.feature_id = $1 AND
  137. CVTFR.name = $2
  138. ';
  139. $status = tripal_core_chado_prepare('feature_rel_get_subject', $psql, array('int', 'text'));
  140. if (!$status) {
  141. tripal_report_error('tripal_feature', TRIPAL_ERROR, "init: not able to prepare SQL statement '%name'",
  142. array('%name' => 'feature_by_object'));
  143. }
  144. $sql = "EXECUTE feature_rel_get_subject(:feature_id, :relationship)";
  145. $features = chado_query($sql, array(':feature_id' => $feature_id, ':relationship' => $relationship));
  146. }
  147. $sequences = '';
  148. while ($feature = $features->fetchObject()) {
  149. // recurse and get the sequences for these in the relationship
  150. if ($rel_part == "subject") {
  151. $defline = "$feature_name, $relationship, $feature->uniquename $feature->feature_type ($feature->genus $feature->species)";
  152. }
  153. if ($rel_part == "object") {
  154. $defline = "$feature->uniquename $feature->feature_type ($feature->genus $feature->species), $relationship, $feature_name";
  155. }
  156. $sequences .= tripal_feature_get_formatted_sequence($feature->feature_id, $defline,
  157. $num_bases_per_line, $derive_from_parent, $aggregate, $output_format,
  158. $upstream, $downstream, $sub_features, '', '');
  159. }
  160. return $sequences;
  161. }
  162. // prepare statements we'll need to use later
  163. if (!tripal_core_is_sql_prepared('sequence_by_parent')) {
  164. // prepare the queries we're going to use later during the render phase
  165. // This SQL statement uses conditionals in the select clause to handle
  166. // cases cases where the alignment is in the reverse direction and when
  167. // the upstream and downstream extensions go beyond the lenght of the
  168. // parent sequence.
  169. $psql ='
  170. PREPARE sequence_by_parent (int, int, int) AS
  171. SELECT srcname, srcfeature_id, strand, srctypename, typename,
  172. fmin, fmax, upstream, downstream, adjfmin, adjfmax,
  173. substring(residues from (adjfmin + 1) for (upstream + (fmax - fmin) + downstream)) as residues,
  174. genus, species
  175. FROM (
  176. SELECT
  177. OF.name srcname, FL.srcfeature_id, FL.strand,
  178. OCVT.name as srctypename, SCVT.name as typename,
  179. FL.fmin, FL.fmax, OO.genus, OO.species,
  180. CASE
  181. WHEN FL.strand >= 0 THEN
  182. CASE
  183. WHEN FL.fmin - $1 <= 0 THEN 0
  184. ELSE FL.fmin - $1
  185. END
  186. WHEN FL.strand < 0 THEN
  187. CASE
  188. WHEN FL.fmin - $2 <= 0 THEN 0
  189. ELSE FL.fmin - $2
  190. END
  191. END as adjfmin,
  192. CASE
  193. WHEN FL.strand >= 0 THEN
  194. CASE
  195. WHEN FL.fmax + $2 > OF.seqlen THEN OF.seqlen
  196. ELSE FL.fmax + $2
  197. END
  198. WHEN FL.strand < 0 THEN
  199. CASE
  200. WHEN FL.fmax + $1 > OF.seqlen THEN OF.seqlen
  201. ELSE FL.fmax + $1
  202. END
  203. END as adjfmax,
  204. CASE
  205. WHEN FL.strand >= 0 THEN
  206. CASE
  207. WHEN FL.fmin - $1 <= 0 THEN FL.fmin
  208. ELSE $1
  209. END
  210. ELSE
  211. CASE
  212. WHEN FL.fmax + $1 > OF.seqlen THEN OF.seqlen - FL.fmax
  213. ELSE $1
  214. END
  215. END as upstream,
  216. CASE
  217. WHEN FL.strand >= 0 THEN
  218. CASE
  219. WHEN FL.fmax + $2 > OF.seqlen THEN OF.seqlen - FL.fmax
  220. ELSE $2
  221. END
  222. ELSE
  223. CASE
  224. WHEN FL.fmin - $2 <= 0 THEN FL.fmin
  225. ELSE $2
  226. END
  227. END as downstream,
  228. OF.residues
  229. FROM {featureloc} FL
  230. INNER JOIN {feature} SF on FL.feature_id = SF.feature_id
  231. INNER JOIN {cvterm} SCVT on SF.type_id = SCVT.cvterm_id
  232. INNER JOIN {feature} OF on FL.srcfeature_id = OF.feature_id
  233. INNER JOIN {cvterm} OCVT on OF.type_id = OCVT.cvterm_id
  234. INNER JOIN {organism} OO on OF.organism_id = OO.organism_id
  235. WHERE SF.feature_id = $3 and NOT (OF.residues = \'\' or OF.residues IS NULL)) as tbl1
  236. ';
  237. $status = tripal_core_chado_prepare('sequence_by_parent', $psql, array('int', 'int', 'int'));
  238. if (!$status) {
  239. tripal_report_error('tripal_feature', TRIPAL_ERROR,
  240. "init: not able to prepare SQL statement '%name'",
  241. array('%name' => 'sequence_by_parent'));
  242. }
  243. // this query is meant to get all of the sub features of any given
  244. // feature (arg #1) and order them as they appear on the reference
  245. // feature (arg #2).
  246. $psql ='PREPARE sub_features (int, int) AS
  247. SELECT SF.feature_id, CVT.name as type_name, SF.type_id
  248. FROM {feature_relationship} FR
  249. INNER JOIN {feature} SF on SF.feature_id = FR.subject_id
  250. INNER JOIN {cvterm} CVT on CVT.cvterm_id = SF.type_id
  251. INNER JOIN {featureloc} FL on FL.feature_id = FR.subject_id
  252. INNER JOIN {feature} PF on PF.feature_id = FL.srcfeature_id
  253. WHERE FR.object_id = $1 and PF.feature_id = $2
  254. ORDER BY FL.fmin ASC';
  255. $status = tripal_core_chado_prepare('sub_features', $psql, array('int', 'int'));
  256. if (!$status) {
  257. tripal_report_error('tripal_feature', TRIPAL_ERROR,
  258. "init: not able to prepare SQL statement '%name'",
  259. array('%name' => 'ssub_features'));
  260. }
  261. $psql ='PREPARE count_sub_features (int, int) AS
  262. SELECT count(*) as num_children
  263. FROM {feature_relationship} FR
  264. INNER JOIN {feature} SF on SF.feature_id = FR.subject_id
  265. INNER JOIN {cvterm} CVT on CVT.cvterm_id = SF.type_id
  266. INNER JOIN {featureloc} FL on FL.feature_id = FR.subject_id
  267. INNER JOIN {feature} PF on PF.feature_id = FL.srcfeature_id
  268. WHERE FR.object_id = $1 and PF.feature_id = $2';
  269. $status = tripal_core_chado_prepare('count_sub_features', $psql, array('int', 'int'));
  270. if (!$status) {
  271. tripal_report_error('tripal_feature', TRIPAL_ERROR,
  272. "init: not able to prepare SQL statement '%name'",
  273. array('%name' => 'count_sub_features'));
  274. }
  275. }
  276. // if we need to get the sequence from the parent then do so now.
  277. if ($derive_from_parent) {
  278. // execute the query to get the sequence from the parent
  279. $sql = "EXECUTE sequence_by_parent (:upstream, :downstream, :feature_id)";
  280. $parents = chado_query($sql, array(':uptream' => $upstream, ':downstream' => $downstream, ':feature_id' => $feature_id));
  281. while ($parent = $parents->fetchObject()) {
  282. $seq = ''; // initialize the sequence for each parent
  283. // if we are to aggregate then we will ignore the feature returned
  284. // by the query above and rebuild it using the sub features
  285. if ($aggregate) {
  286. // now get the sub features that are located on the parent.
  287. $sql = "EXECUTE sub_features (:feature_id, :srcfeature_id)";
  288. $children = chado_query($sql, array(':feature_id' => $feature_id, ':srcfeature_id' => $parent->srcfeature_id));
  289. $sql = "EXECUTE count_sub_features (:feature_id, :srcfeature_id)";
  290. $sub_features = chado_query($sql, array(':feature_id' => $feature_id, ':srcfeature_id' => $parent->srcfeature_id));
  291. $num_children = $sub_features->fetchObject();
  292. // iterate through the sub features and concat their sequences. They
  293. // should already be in order.
  294. $types = array();
  295. $i = 0;
  296. while ($child = $children->fetchObject()) {
  297. // if the callee has specified that only certain sub features should be
  298. // included then continue if this child is not one of those allowed
  299. // subfeatures
  300. if (count($sub_features) > 0 and !in_array($child->type_name, $sub_features)) {
  301. continue;
  302. }
  303. // keep up with the types
  304. if (!in_array($child->type_name, $types)) {
  305. $types[] = $child->type_name;
  306. }
  307. $sql = "EXECUTE sequence_by_parent (:upstream, %d, :feature_id)";
  308. // if the first sub feature we need to include the upstream bases. first check if
  309. // the feature is in the foward direction or the reverse.
  310. if ($i == 0 and $parent->strand >= 0) { // forward direction
  311. // -------------------------- ref
  312. // ....----> ---->
  313. // up 1 2
  314. $q = chado_query($sql, array(':upstream' => $upstream, ':downstream' => 0, ':feature_id' => $child->feature_id));
  315. }
  316. elseif ($i == 0 and $parent->strand < 0) { // reverse direction
  317. // -------------------------- ref
  318. // ....<---- <----
  319. // down 1 2
  320. $q = chado_query($sql, array(':upstream' => 0, ':downstream' => $downstream, ':feature_id' => $child->feature_id));
  321. }
  322. // Next, if the last sub feature we need to include the downstream bases. first check if
  323. // the feature is in teh forward direction or the reverse
  324. if ($i == $num_children->num_children - 1 and $parent->strand >= 0) { // forward direction
  325. // -------------------------- ref
  326. // ----> ---->....
  327. // 1 2 down
  328. $q = chado_query($sql, array(':upstream' => 0, ':downstream' => $downstream, ':feature_id' => $child->feature_id));
  329. }
  330. elseif ($i == $num_children->num_children - 1 and $parent->strand < 0) { // reverse direction
  331. // -------------------------- ref
  332. // <---- <----....
  333. // 1 2 up
  334. $q = chado_query($sql, array(':upstream' => $upstream, ':downstream' => 0, ':feature_id' => $child->feature_id));
  335. }
  336. // for internal sub features we don't want upstream or downstream bases
  337. else {
  338. $sql = "EXECUTE sequence_by_parent (%d, %d, %d)";
  339. $q = chado_query($sql, array(':upstream' => 0, ':downstream' => 0, ':feature_id' => $child->feature_id));
  340. }
  341. while ($subseq = $q->fetchObject()) {
  342. // concatenate the sequences of all the sub features
  343. if ($subseq->srcfeature_id == $parent->srcfeature_id) {
  344. $seq .= $subseq->residues;
  345. }
  346. }
  347. $i++;
  348. }
  349. }
  350. // if this isn't an aggregate then use the parent residues
  351. else {
  352. $seq = $parent->residues;
  353. }
  354. // get the reverse compliment if feature is on the reverse strand
  355. $dir = 'forward';
  356. if ($parent->strand < 0) {
  357. $seq = tripal_feature_reverse_complement($seq);
  358. $dir = 'reverse';
  359. }
  360. // now format for display
  361. if ($output_format == 'fasta_html') {
  362. $seq = wordwrap($seq, $num_bases_per_line, "<br>", TRUE);
  363. }
  364. elseif ($output_format == 'fasta_txt') {
  365. $seq = wordwrap($seq, $num_bases_per_line, "\r\n", TRUE);
  366. }
  367. $residues .= ">$feature_name. Sequence derived from feature of type, '$parent->srctypename', of $parent->genus $parent->species: $parent->srcname:" . ($parent->adjfmin + 1) . ".." . $parent->adjfmax . " ($dir). ";
  368. if (count($types) > 0) {
  369. $residues .= "Excludes all bases but those of type(s): " . implode(', ', $types) . ". " ;
  370. }
  371. if ($parent->upstream > 0) {
  372. $residues .= "Includes " . $parent->upstream . " bases upstream. ";
  373. }
  374. if ($parent->downstream > 0) {
  375. $residues .= "Includes " . $parent->downstream . " bases downstream. ";
  376. }
  377. if (!$seq) {
  378. if ($output_format == 'fasta_html') {
  379. $residues .= "No sequence available.</br>";
  380. }
  381. else {
  382. $residues .= "No sequence available.\r\n";
  383. }
  384. }
  385. else {
  386. if ($output_format == 'fasta_html') {
  387. $residues .= "<br>";
  388. }
  389. $residues .= "\r\n" . $seq . "\r\n";
  390. if ($output_format == 'fasta_html') {
  391. $residues .= "<br>";
  392. }
  393. }
  394. }
  395. }
  396. // if we are not getting the sequence from the parent sequence then
  397. // use what comes through from the feature record
  398. else {
  399. $sql = "SELECT * FROM {feature} F WHERE feature_id = :feature_id";
  400. $values = chado_query($sql, array(':feature_id' => $feature_id))->fetchObject();
  401. $residues = $values->residues;
  402. if ($output_format == 'fasta_html') {
  403. $residues = wordwrap($residues, $num_bases_per_line, "<br>", TRUE);
  404. }
  405. elseif ($output_format == 'fasta_txt') {
  406. $residues = wordwrap($residues, $num_bases_per_line, "\r\n", TRUE);
  407. }
  408. $residues = ">$feature_name\r\n$residues\r\n";
  409. }
  410. // format the residues for display
  411. if ($residues and $num_bases_per_line) {
  412. if ($output_format == 'fasta_html') {
  413. $residues = '<span style="font-family: monospace;">' . $residues . '</span>';
  414. }
  415. }
  416. return $residues;
  417. }
  418. /**
  419. * Returns a fasta record for the passed in feature
  420. *
  421. * @param $feature
  422. * A single feature object containing all the fields from the chado.feature table
  423. * @param $desc
  424. * A string containing any additional details you want added to the definition line of
  425. * the fasta record.
  426. *
  427. * Note: the feature accession and name separated by a | will be added
  428. * before the description but on the same line
  429. *
  430. * @ingroup tripal_feature_api
  431. */
  432. function tripal_get_fasta_sequence($feature, $desc) {
  433. $fasta = ">" . variable_get('chado_feature_accession_prefix', 'FID') . "$feature->feature_id|$feature->name";
  434. $fasta .= " $desc\n";
  435. $fasta .= wordwrap($feature->residues, 50, "\n", TRUE);
  436. $fasta .= "\n\n";
  437. return $fasta;
  438. }