changing over to stock tables (from accession)
[cxgn-corelibs.git] / lib / CXGN / Marker / PCR / Experiment.pm
blob75b207f7aed70347c9b535c55973d380f282c2d4
2 =head1 NAME
4 CXGN::Marker::PCR::Experiment
6 =head1 AUTHOR
8 John Binns <zombieite@gmail.com>
10 =head1 DESCRIPTION
12 PCR experiment object for both retrieving and inserting marker experiment data.
14 =cut
16 use strict;
18 package CXGN::Marker::PCR::Experiment;
19 use Carp;
20 use CXGN::Marker;
21 ###use CXGN::Accession;
22 use CXGN::Tools::Text;
23 use Array::Compare;
24 use CXGN::DB::SQLWrappers;
25 use CXGN::Marker::Tools;
26 use CXGN::DB::Connection;
28 =head2 new
30 my $experiment_for_viewing=CXGN::Marker::PCR::Experiment->new($dbh,$pcr_experiment_id);
31 my $experiment_for_storing=CXGN::Marker::PCR::Experiment->new($dbh);
33 =cut
35 sub new {
36 my $class = shift;
37 my ( $dbh, $pcr_experiment_id ) = @_;
38 my $self = bless( {}, $class );
39 if ( CXGN::DB::Connection::is_valid_dbh($dbh) ) {
40 $self->{dbh} = $dbh;
42 else {
43 croak("'$dbh' is not a valid dbh");
45 if ($pcr_experiment_id) {
47 #find experiment data
48 my $pcr_query = $self->{dbh}->prepare( "
49 SELECT
50 marker_experiment.marker_id,
51 marker_experiment.location_id,
52 pcr_experiment.pcr_experiment_id,
53 primer_id_fwd,
54 primer_id_rev,
55 primer_id_pd,
56 primer_type,
57 mg_concentration,
58 annealing_temp,
59 additional_enzymes,
60 protocol,
61 predicted,
62 stock_id
63 FROM
64 pcr_experiment
65 left join marker_experiment using (pcr_experiment_id)
66 WHERE
67 pcr_experiment_id=?
68 " );
69 $pcr_query->execute($pcr_experiment_id);
70 my $pcr_hashref = $pcr_query->fetchrow_hashref();
72 # This was causing the page to die for markers 9654 and 9615. Not sure why this problem should
73 # suddenly turn up. These markers had entries in pcr_experiment but not in marker_experiment,
74 # so John considered them valid, but "orphan" experiments. There is a query in CXGN/Marker.pm
75 # that specifically queries for orphan experiments. That's fine, but then the following check
76 # fails. For now I'm just changing the field it checks. This shouldn't break anything. -beth, 2007-03-21
77 # unless($pcr_hashref->{marker_id})
78 unless ( $pcr_hashref->{pcr_experiment_id} ) {
79 croak
80 "Orphan PCR experiment object created with ID of '$pcr_experiment_id'--there is no marker_experiment entry for this experiment";
82 unless ( $pcr_hashref->{pcr_experiment_id} ) {
83 croak "PCR experiment not found with ID of '$pcr_experiment_id'";
85 while ( my ( $key, $value ) = each %$pcr_hashref ) {
86 $self->{$key} = $value;
88 $self->{predicted} ? $self->{predicted} = 't' : $self->{predicted} =
89 'f';
91 #get primers, if they are present
92 my $q =
93 $dbh->prepare('select sequence from sequence where sequence_id=?');
94 $q->execute( $self->{primer_id_fwd} );
95 ( $self->{fwd_primer} ) = $q->fetchrow_array();
96 $q->execute( $self->{primer_id_rev} );
97 ( $self->{rev_primer} ) = $q->fetchrow_array();
99 $q->execute( $self->{primer_id_pd} );
100 ( $self->{dcaps_primer} ) = $q->fetchrow_array();
102 #get pcr products
103 my $sizes;
104 $q = $dbh->prepare(
105 "SELECT stock.stock_id,band_size,multiple_flag FROM pcr_exp_accession inner join pcr_product using(pcr_exp_accession_id) inner join stock on(pcr_exp_accession.stock_id=stock.stock_id) WHERE enzyme_id is null and pcr_experiment_id=?"
107 $q->execute( $self->{pcr_experiment_id} );
108 $sizes = $q->fetchall_arrayref();
109 if ( $sizes->[0] ) {
110 $self->{pcr_bands} = $self->query_results_to_bands_hash($sizes);
112 $q = $dbh->prepare(
113 "SELECT stock.stock_id,band_size,multiple_flag FROM pcr_exp_accession inner join pcr_product using(pcr_exp_accession_id) inner join stock on(pcr_exp_accession.stock_id=stock.stock_id) WHERE enzyme_id is not null and pcr_experiment_id=?"
115 $q->execute( $self->{pcr_experiment_id} );
116 $sizes = $q->fetchall_arrayref();
117 if ( $sizes->[0] ) {
118 $self->{pcr_digest_bands} =
119 $self->query_results_to_bands_hash($sizes);
122 #get enzyme
123 $q = $dbh->prepare(
124 "SELECT enzyme_id,enzyme_name FROM pcr_exp_accession inner join pcr_product using(pcr_exp_accession_id) inner join enzymes using(enzyme_id) where pcr_experiment_id=?"
126 $q->execute( $self->{pcr_experiment_id} );
127 ( $self->{enzyme_id}, $self->{enzyme} ) = $q->fetchrow_array()
128 ; #only fetching one row, because they all should be the same. there should be both db and api constraints ensuring that.
130 else #else we're creating and empty object
133 #initialize empty object--we want some things to have a default value, so the object it will be consistent
134 #and able to be worked with even if you haven't set its predicted field, for instance
135 $self->{predicted} = 'f';
138 return $self;
141 =head2 pcr_experiment_id
143 my $id=$experiment->pcr_experiment_id();
145 Returns the PCR experiment ID for this experiment. This cannot be set. It is set when the object is initially retrieved, or when it is stored.
147 =cut
149 #this function cannot be used as a setter, since this id is assigned by the database
150 sub pcr_experiment_id {
151 my $self = shift;
152 return $self->{pcr_experiment_id};
155 =head2 equals
157 my $experiment=CXGN::Marker::PCR::Experiment->new($dbh,$pcr_experiment_id);
158 my $experiment_for_comparison=CXGN::Marker::PCR::Experiment->new($dbh,$possible_match_id);
159 if($experiment->equals($experiment_for_comparison)){print"They are the same!";}
161 =cut
163 ##########################################
164 # compare this pcr experiment with another
165 ##########################################
167 sub check_pcr_band_arrays {
168 my ( $stock, $pcr_hash_1, $pcr_hash_2 ) = @_;
170 my $comp = Array::Compare->new();
172 my $croaking =
173 "PCR bands (or digest bands) entry for stock '$stock' does not appear to be array ref";
175 unless ( $pcr_hash_1->{$stock} and $pcr_hash_2->{$stock} ) { return 0 }
176 unless ( ( ref( $pcr_hash_1->{$stock} ) eq 'ARRAY' ) ) { croak $croaking }
177 unless ( ( ref( $pcr_hash_2->{$stock} ) eq 'ARRAY' ) ) { croak $croaking }
179 # Array::Compare::perm returns true if lists are the same or permutations of each other (bands may have been stored in any order)
180 unless ( $comp->perm( $pcr_hash_1->{$stock}, $pcr_hash_2->{$stock} ) ) {
181 return 0;
184 return 1;
187 sub equals {
188 my $self = shift;
189 my ($other) = @_;
191 unless ( $other->isa('CXGN::Marker::PCR::Experiment') ) {
192 croak "Must send in a PCR experiment object to equals function";
194 unless ( $self->marker_id() and $other->marker_id() ) {
195 croak
196 "Must set both PCR experiment objects' marker IDs before comparing\n-----\nself:\n"
197 . $self->as_string()
198 . "-----\nother:\n"
199 . $other->as_string();
201 unless ( $self->protocol() and $other->protocol() ) {
202 croak
203 "Must set both PCR experiment objects' protocols before comparing";
205 unless ( $self->predicted() eq 'f' or $self->predicted() eq 't' ) {
206 croak "Can't check for equality; invalid predicted field for self:\n"
207 . $self->as_string();
209 unless ( $other->predicted() eq 'f' or $other->predicted() eq 't' ) {
210 croak
211 "Can't check for equality; invalid predicted field for other object:\n"
212 . $other->as_string();
214 if ( $self->marker_id() ne $other->marker_id() ) { return 0 }
215 if ( $self->fwd_primer() ne $other->fwd_primer() ) { return 0 }
216 if ( $self->rev_primer() ne $other->rev_primer() ) { return 0 }
217 if ( $self->primer_type() ne $other->primer_type() ) { return 0 }
218 if ( $self->enzyme() ne $other->enzyme() ) { return 0 }
219 if ( $self->predicted() ne $other->predicted() ) { return 0 }
220 if ( $self->protocol eq 'RFLP' and $other->protocol ne 'RFLP' ) { return 0 }
221 if ( $self->protocol ne 'RFLP' and $other->protocol eq 'RFLP' ) { return 0 }
223 my $pcr_hash_1 = $self->{pcr_bands};
224 my $pcr_hash_2 = $other->{pcr_bands};
226 # remove empty keys
227 for my $k ( keys( %{$pcr_hash_1} ) ) {
228 delete $pcr_hash_1->{$k} if ( @{ $pcr_hash_1->{$k} } == 0 );
230 for my $k ( keys( %{$pcr_hash_2} ) ) {
231 delete $pcr_hash_2->{$k} if ( @{ $pcr_hash_2->{$k} } == 0 );
234 # check pcr band arrays for all stocks in first object
235 for my $stock ( keys( %{$pcr_hash_1} ) ) {
236 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
237 return 0;
241 # then check pcr band arrays for all stocks in second object, in case the second has stocks the first doesn't
242 for my $stock ( keys( %{$pcr_hash_2} ) ) {
243 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
244 return 0;
248 $pcr_hash_1 = $self->{pcr_digest_bands};
249 $pcr_hash_2 = $other->{pcr_digest_bands};
251 # remove empty keys
252 for my $k ( keys( %{$pcr_hash_1} ) ) {
253 delete $pcr_hash_1->{$k} if ( @{ $pcr_hash_1->{$k} } == 0 );
255 for my $k ( keys( %{$pcr_hash_2} ) ) {
256 delete $pcr_hash_2->{$k} if ( @{ $pcr_hash_2->{$k} } == 0 );
259 # check pcr digest band arrays for all stocks in first object
260 for my $stock ( keys( %{$pcr_hash_1} ) ) {
261 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
262 return 0;
266 # then check pcr digest band arrays for all accessions in second object, in case the second has accessions the first doesn't
267 for my $stock ( keys( %{$pcr_hash_2} ) ) {
268 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
269 return 0;
273 #only compare mg and temp IF they are present in BOTH objects... see note below
274 if ( ( $self->mg_conc() and $other->mg_conc() )
275 and ( $self->mg_conc() != $other->mg_conc() ) )
277 return 0;
279 if ( ( $self->temp() and $other->temp() )
280 and ( $self->temp() != $other->temp() ) )
282 return 0;
285 #notes:
286 #we did not compare missing temperature or mg concentration values, because yimin says experiments that are so similar
287 #that the only difference is that one is missing a temp or mg conc are the same experiment
288 #we did not compare additional_enzymes, because this is just a long text notes field, not essential data for the experiment,
289 #and frequently subject to minor changes in its text. this is just feinan's extra COSII PCR data field.
291 return 1;
294 =head2 exists
296 Returns its pcr_experiment_id if it already exists in the database, or undef if not.
298 =cut
300 ###################
301 # storing functions
302 ###################
303 sub exists {
304 my $self = shift;
305 unless ( $self->{marker_id} ) {
306 croak
307 "Cannot test for an experiment's existence without knowing which marker it goes with--store marker and set experiment's marker ID before storing experiments";
309 unless ( $self->{protocol} ) {
310 croak
311 "I doubt an experiment like this one exists, since it has no experiment protocol. Set to unknown if necessary.";
313 if ( $self->{pcr_experiment_id} ) {
315 #warn"I think it's pretty obvious that this experiment exists, since it seems to have been loaded from the database, or recently stored to the database--it already has an id of $self->{pcr_experiment_id}";
316 return $self->{pcr_experiment_id};
318 unless ( $self->predicted() eq 'f' or $self->predicted() eq 't' ) {
319 croak "Can't check for existence; invalid predicted field for self:\n"
320 . $self->as_string();
322 my $possible_matches_query = $self->{dbh}->prepare( "
323 SELECT
324 pcr_experiment_id
325 FROM
326 marker_experiment
327 WHERE
328 marker_id=?
329 and pcr_experiment_id is not null
330 " );
331 $possible_matches_query->execute( $self->marker_id() );
332 while ( my ($possible_match_id) =
333 $possible_matches_query->fetchrow_array() )
336 #print"possible match id: $possible_match_id\n";
337 my $experiment_for_comparison =
338 CXGN::Marker::PCR::Experiment->new( $self->{dbh},
339 $possible_match_id );
340 if ( $self->equals($experiment_for_comparison) ) {
341 $self->{pcr_experiment_id} = $experiment_for_comparison
342 ->{pcr_experiment_id}; #ok, we've been found to already exist, so set our pcr_experiment_id
343 return $self->{pcr_experiment_id};
346 return;
349 =head2 store_unless_exists
351 Stores this experiment in the database, as long as it does not exist. If it does not exist and it is stored, this function will return its new pcr_experiment_id. If the experiment does exists, it will set the pcr_experiment_id but NOT return it.
353 =cut
355 sub store_unless_exists {
356 my $self = shift;
358 if ( $self->exists() ) { return }
360 unless ( $self->{marker_id} ) {
361 croak "Cannot store experiment without marker ID";
363 unless ( $self->{protocol} ) {
364 croak
365 "Cannot store experiment without protocol. Use 'unknown' if necessary.";
367 unless ( $self->predicted() eq 'f' or $self->predicted() eq 't' ) {
368 croak "Can't store; invalid predicted field for self:\n"
369 . $self->as_string();
371 if ( $self->{pcr_experiment_id} ) {
372 croak
373 "This experiment appears to have been stored already or created from an existing database entry";
375 ##################### TODO #########################
376 #if we already have a PCR experiment ID, and someone
377 #calls 'store_unless_exists', this is a perfectly
378 #reasonable use case, but i have not implemented it yet.
379 #they might want to modify an existing experiment. for
380 #instance, it is common that someone might add digested
381 #bands later, after having loaded an experiment with
382 #only regular pcr bands a few months before. this object cannot yet handle this
383 #situation. that is why it croaks here. if you need to add
384 #this functionality, add it here. it would consist of some
385 #kind of object integrity checking and checking for values
386 #which have been added or modified and adding or modifying
387 #those same values in the database. alternatively, you may
388 #just want to write another class--CXGN::Marker::PCR::Experiment::Modfiable
389 #or something like that which has fewer checks and just
390 #directly accesses data in the database using an object
391 #like lukas's modifiable form object.
392 if ( $self->{pcr_digest_bands} ) {
393 unless ( $self->{enzyme_id} ) {
394 croak "Must have an enzyme set to store digest bands";
398 my $dbh = $self->{dbh};
399 my $sql = CXGN::DB::SQLWrappers->new( $self->{dbh} );
401 if ( $self->fwd_primer() ) {
402 my $fwd_info =
403 $sql->insert_unless_exists( 'sequence',
404 { 'sequence' => $self->fwd_primer() } );
405 $self->{fwd_primer_id} = $fwd_info->{id};
407 if ( $self->rev_primer() ) {
408 my $rev_info =
409 $sql->insert_unless_exists( 'sequence',
410 { 'sequence' => $self->rev_primer() } );
411 $self->{rev_primer_id} = $rev_info->{id};
414 #print"INSERTING:\n".$self->as_string();
416 my $pcr_exp_insert = $self->{dbh}->prepare( '
417 insert into sgn.pcr_experiment (
418 mg_concentration,
419 annealing_temp,
420 primer_id_fwd,
421 primer_id_rev,
422 primer_type,
423 additional_enzymes,
424 predicted
426 values (?,?,?,?,?,?,?)
427 ' );
428 $pcr_exp_insert->execute(
429 $self->{mg_concentration}, $self->{annealing_temp},
430 $self->{fwd_primer_id}, $self->{rev_primer_id},
431 $self->{primer_type}, $self->{additional_enzymes},
432 $self->{predicted}
434 $self->{pcr_experiment_id} = $self->{dbh}->last_insert_id('pcr_experiment')
435 or croak "Could not get last_insert_id from pcr_experiment";
437 my %stocks;
438 for my $stock (
439 keys( %{ $self->{pcr_bands} } ),
440 keys( %{ $self->{pcr_digest_bands} } )
443 $stocks{$stock} = 0;
446 # dummy value for now, until we get a pcr_exp_accession_id
448 my $exp_acc_insert =
449 $self->{dbh}->prepare(
450 'insert into sgn.pcr_exp_accession (pcr_experiment_id,stock_id) values (?,?)'
452 my $pcr_band_insert =
453 $self->{dbh}->prepare(
454 'insert into sgn.pcr_product (pcr_exp_accession_id,enzyme_id,multiple_flag,band_size,predicted) values (?,?,?,?,?)'
457 for my $stock_id ( keys(%stocks) ) {
458 $exp_acc_insert->execute( $self->{pcr_experiment_id}, $stock_id );
459 $stocks{$stock_id} = $self->{dbh}->last_insert_id('pcr_exp_accession')
460 or croak "Could not get last_insert_id from pcr_exp_accession";
462 my @stock_pcr_bands;
463 my @stock_pcr_digest_bands;
464 if ( $self->{pcr_bands}->{$stock_id} ) {
465 @stock_pcr_bands = @{ $self->{pcr_bands}->{$stock_id} };
467 if ( $self->{pcr_digest_bands}->{$stock_id} ) {
468 @stock_pcr_digest_bands =
469 @{ $self->{pcr_digest_bands}->{$stock_id} };
471 if ( $stock_pcr_bands[0] ) { #if there is at least one value in the pcr bands list for this stock
472 for my $band (@stock_pcr_bands) {
474 #if the band entry starts with an m, it means multiple bands, so set the multiple flag. no enzyme insert for regular pcr bands.
475 if ( $band =~ /^m/i ) {
476 $pcr_band_insert->execute( $stocks{$stock_id}, undef, 1,
477 undef, $self->{predicted} );
479 else {
480 $pcr_band_insert->execute( $stocks{$stock_id}, undef, undef,
481 $band, $self->{predicted} );
485 if ( $stock_pcr_digest_bands[0] ) {
486 # if there is at least one value in the pcr digest bands list for this stock
487 #if the band entry starts with an m, it means multiple bands, so set the multiple flag.
488 for my $band (@stock_pcr_digest_bands) {
489 if ( $band =~ /^m/i ) {
490 $pcr_band_insert->execute( $stocks{$stock_id},
491 $self->{enzyme_id}, 1, undef, $self->{predicted} );
493 else {
494 $pcr_band_insert->execute( $stocks{$stock_id},
495 $self->{enzyme_id}, undef, $band, $self->{predicted} );
501 #and now for a final test of this object
502 if ( my $oops_id = $self->store_unless_exists() ) {
503 my $croaking =
504 "Oops, this object isn't working correctly. Immediately after being stored with ID "
505 . "'$self->{pcr_experiment_id}', it tried to store itself again as a test, and succeeded with ID '$oops_id' "
506 . "(it should have failed, because it was already inserted!)";
507 croak $croaking;
510 return $self->{pcr_experiment_id};
513 =head2 update_additional_enzymes
515 #this will actually update the pcr experiment entry in the database
516 $experiment->update_additional_enzymes('All possible enzymes for blah blah blah are blah blah blah....');
518 =cut
520 #storing function for additional_enzymes field. this data is not essential to the experiment. it is just a text field with
521 #notes that feinan wants to show up for cosii markers, so it has no special checks or anything.
522 sub update_additional_enzymes {
523 my $self = shift;
524 my ($additional_enzymes) = @_;
525 if ( length($additional_enzymes) > 1023 ) {
526 croak
527 "Additional enzymes field contents size limit is exceeded by string '$additional_enzymes'";
529 unless ( $self->{pcr_experiment_id} ) {
530 croak
531 "This experiment object does not appear to have been loaded or inserted into the database yet, so you cannot update its enzymes";
533 my $sth =
534 $self->{dbh}->prepare(
535 'update pcr_experiment set additional_enzymes=? where pcr_experiment_id=?'
537 $sth->execute( $additional_enzymes, $self->{pcr_experiment_id} );
540 =head2 as_string
542 #print out the whole experiment, for debugging, or for loading script output
543 print $experiment->as_string();
545 =cut
547 #######################
548 # display for debugging
549 #######################
550 sub as_string {
551 my $self = shift;
552 my $string = "";
553 $string .= "<pcr_experiment>\n";
554 my @marker_names;
555 if ( $self->{marker_id} ) {
556 @marker_names =
557 CXGN::Marker->new( $self->{dbh}, $self->{marker_id} )
558 ->name_that_marker();
560 else {
561 @marker_names = ('<no marker associated yet>');
563 $string .= "\tMarker: @marker_names\n";
564 $string .= "\tPCR experiment ID: $self->{pcr_experiment_id}\n";
565 if ( $self->{location_id} ) {
566 $string .=
567 "\tThis is a mapping experiment; location ID: $self->{location_id}\n";
569 else {
570 $string .=
571 "\tThis experiment does not yet have a map location associated with it in the marker_experiment table\n";
573 $string .= "\tProtocol: $self->{protocol}\n";
574 $string .=
575 "\tPrimers: $self->{fwd_primer} (fwd)\t\t$self->{rev_primer} (rev)\n";
576 my $pt = $self->{primer_type};
577 $pt ||= '';
578 $string .= "\tPrimer type: $pt\n";
579 my $mg = $self->{mg_concentration};
580 $mg ||= '';
581 my $temp = $self->{annealing_temp};
582 $temp ||= '';
583 $string .= "\tConditions: $mg MG - $temp C\n";
584 if ( $self->{enzyme} ) { $string .= "\tEnzyme: $self->{enzyme}\n"; }
585 $string .= "\tBands:\n";
586 my $bands = $self->pcr_bands_hash_of_strings();
588 if ( $bands and %{$bands} ) {
589 for my $stock ( keys( %{$bands} ) ) {
591 #$string.="\t".CXGN::Accession->new($self->{dbh},$accession)->extra_verbose_name().": ".$bands->{$accession}."\n";
592 $string .= "\t$stock : " . $bands->{$stock} . "\n";
595 $string .= "\tDigest bands:\n";
596 $bands = $self->pcr_digest_bands_hash_of_strings();
597 if ( $bands and %{$bands} ) {
598 for my $stock ( keys( %{$bands} ) ) {
599 $string .= "\t$stock : " . $bands->{$stock} . "\n";
602 $string .= "\tPredicted: $self->{predicted}\n";
603 $string .= "</pcr_experiment>\n";
606 =head2 query_results_to_bands_hash
608 For internal use. Converts bands query results into a form that can be stored easily.
610 =cut
612 ###########################################
613 # helpful functions mainly for internal use
614 ###########################################
615 sub query_results_to_bands_hash {
616 my $self = shift;
617 my ($sizes) = @_;
618 my %bands;
619 for my $row ( @{$sizes} ) {
620 my ( $stock, $band_size, $multiple_flag ) = @{$row};
621 if ( $stock and ( $band_size or $multiple_flag ) ) {
622 my $insert_value;
623 if ($band_size) {
624 $insert_value = $band_size;
626 elsif ($multiple_flag) {
627 $insert_value = 'Multiple';
629 push( @{ $bands{$stock} }, $insert_value );
631 else {
632 croak "Unable to load bands hash";
635 return \%bands;
638 =head2 join_bands_hash
640 For internal use. Converts bands into a more useful form.
642 =cut
644 sub join_bands_hash {
645 my $self = shift;
646 my ($bands_hash) = @_;
647 my %expected_structure;
648 for my $stock ( keys( %{$bands_hash} ) ) {
649 $expected_structure{$stock} = join( '+', @{ $bands_hash->{$stock} } );
651 if ( keys(%expected_structure) ) #if there are values to return, return them
653 return \%expected_structure;
655 else {
656 return;
660 =head2 marker_id
662 my $id=$experiment->marker_id();
664 Gets the marker_id of marker which is involved in this experiment.
666 $experiment->marker_id($marker_id);
668 Sets the marker_id of marker which is involved in this experiment.
670 =cut
672 ###################
673 # accessors/setters
674 ###################
675 sub marker_id {
676 my $self = shift;
677 my ($value) = @_;
678 if ($value) {
679 unless ( $value =~ /^\d+$/ ) {
680 croak "Marker ID must be a number, not '$value'";
682 unless (
683 CXGN::Marker::Tools::is_valid_marker_id( $self->{dbh}, $value ) )
685 croak "Marker ID '$value' does not exist in the database";
687 $self->{marker_id} = $value;
689 return $self->{marker_id};
692 =head2 fwd_primer
694 Returns or sets the forward primer.
696 =cut
698 sub fwd_primer {
699 my $self = shift;
700 my ($value) = @_;
701 if ($value) {
702 $value = $self->test_and_clean_primer($value);
703 $self->{fwd_primer} = $value;
705 return $self->{fwd_primer};
708 =head2 rev_primer
710 Returns or sets the reverse primer.
712 =cut
714 sub rev_primer {
715 my $self = shift;
716 my ($value) = @_;
717 if ($value) {
718 $value = $self->test_and_clean_primer($value);
719 $self->{rev_primer} = $value;
721 return $self->{rev_primer};
724 sub dcaps_primer {
725 my $self = shift;
726 my ($value) = @_;
727 if ($value) {
728 $value = $self->test_and_clean_primer($value);
729 $self->{dcaps_primer} = $value;
731 return $self->{dcaps_primer};
734 =head2 primer_type
736 Returns or sets the primer type.
738 =cut
740 sub primer_type {
741 my $self = shift;
742 my ($value) = @_;
743 if ($value) {
744 unless ( $value eq 'iUPA' or $value eq 'eUPA' ) {
745 croak "'$value' is not a valid primer type";
747 $self->{primer_type} = $value;
749 return $self->{primer_type};
752 =head2 mg_conc
754 Returns or sets the magnesium concentration.
756 =cut
758 sub mg_conc {
759 my $self = shift;
760 my ($value) = @_;
761 if ($value) {
762 unless ( CXGN::Tools::Text::is_number($value) ) {
763 croak "'$value' is not a valid number for mg concentration";
765 $self->{mg_concentration} = $value;
767 return $self->{mg_concentration};
770 =head2 temp
772 Returns or sets the temperature. If you send in Fahrenheit you must have an 'F' after the degrees. It will convert it to Celsius for you.
774 =cut
776 sub temp {
777 my $self = shift;
778 my ($value) = @_;
779 if ($value) {
780 unless ( $value =~ /^(\d*?\.?\d*?)[cf]?$/i ) {
781 croak "'$value' is an invalid anneal temp";
783 $value =~ s/C$//i; #strip C for Celsius
784 if ( $value =~
785 s/F$//i ) #if it was an F for Fahrenheit, convert it to Celsius
787 $value = ( $value + 40 ) * 5 / 9;
789 $self->{annealing_temp} = $value;
791 return $self->{annealing_temp};
794 =head2 protocol
796 Returns or sets the experiment protocol.
798 =cut
800 sub protocol {
801 my $self = shift;
802 my ($protocol) = @_;
803 if ($protocol) {
804 unless ( $protocol eq 'AFLP'
805 or $protocol eq 'CAPS'
806 or $protocol eq 'RAPD'
807 or $protocol eq 'SNP'
808 or $protocol eq 'SSR'
809 or $protocol eq 'RFLP'
810 or $protocol eq 'PCR'
811 or $protocol eq 'unknown'
812 or $protocol =~ /Indel/i )
814 croak "Protocol '$protocol' is invalid.";
816 if ( $protocol eq 'RFLP' ) {
817 croak "RFLP is not a valid PCR experiment protocol";
819 $self->{protocol} = $protocol;
821 return $self->{protocol};
824 =head2 enzyme
826 Returns or sets the enzyme used to get the digest bands.
828 =cut
830 sub enzyme {
831 my $self = shift;
832 my ($enzyme) = @_;
833 if ($enzyme) {
834 $enzyme =~ s/\s//g unless $enzyme =~ /and/; #clear whitespace
835 $enzyme =~ s/(1+)$/'I' x length($1)/e; #1 -> I
837 # this isn't working for some reason
839 if ( $enzyme eq 'PCR' ) { $enzyme = 'amplicon difference' }
841 # TODO: change this to undef once everything is working
842 elsif ( $enzyme eq 'SNP' ) { $enzyme = 'unknown' }
843 elsif ( !$enzyme ) { $enzyme = 'unknown' }
845 unless ( $self->{enzyme_id} =
846 CXGN::Marker::Tools::get_enzyme_id( $self->{dbh}, $enzyme ) )
848 croak
849 "'$enzyme' is not a valid enzyme (you may need to add it to the enzyme table)";
852 $self->{enzyme} = $enzyme;
855 return $self->{enzyme};
858 =head2 additional_enzymes
860 Returns or sets Feinan^s COSII additional_enzymes field.
862 =cut
864 sub additional_enzymes {
865 my $self = shift;
866 my ($value) = @_;
867 if ($value) { $self->{additional_enzymes} = $value; }
868 return $self->{additional_enzymes};
871 =head2 predicted
873 Returns or sets whether or not the band sizes stored in this object are predicted.
875 =cut
877 sub predicted {
878 my $self = shift;
879 my ($value) = @_;
880 if ($value) {
881 $value = lc($value);
882 unless ( $value eq 't' or $value eq 'f' ) {
883 croak "Predicted must be either 't' or 'f'";
885 $self->{predicted} = $value;
887 return $self->{predicted};
890 =head2 add_pcr_bands_for_stock
892 $experiment->add_pcr_bands_for_stock('250+400','LA716');
894 Note: add_pcr_bands_for_accession is deprecated.
896 =cut
898 #example use: $created_experiment->add_pcr_bands_string_for_accession('750+900','LA925');
900 sub add_pcr_bands_for_accession {
901 warn "add_pcr_bands_for_accession is deprecated\n";
902 shift->add_pcr_bands_for_stock(@_);
905 sub add_pcr_bands_for_stock {
906 my $self = shift;
907 my ( $bands_string, $stock_name ) = @_;
909 # my $accession_object=CXGN::Accession->new($self->{dbh},$accession);
910 # unless($accession_object)
912 # croak"Accession '$stock_name' not found\n";
915 my $stock_id = $self->get_stock_id_with_stock_name($stock_name);
917 #my $accession_id=$accession_object->accession_id();
918 unless ($stock_id) { croak("Accession '$stock_name' not found"); }
919 my @bands = split( /\+/, $bands_string );
920 $self->{pcr_bands}->{$stock_id} = \@bands;
921 $self->{pcr_bands} = $self->test_and_clean_bands( $self->{pcr_bands} );
924 =head2 add_pcr_digest_bands_for_stock
926 $experiment->add_pcr_digest_bands_for_stock('250+400','LA716');
928 Side effect: Dies if the stock is not valid.
929 Note: add_pcr_digest_bands_for_accession is deprecated.
931 =cut
933 sub add_pcr_digest_bands_for_accession {
934 warn "add_pcr_digest_bands_for_accession is deprecated.\n";
935 shift->add_pcr_digest_bands_for_stock(@_);
938 #example use: $created_experiment->add_pcr_digest_bands_string_for_accession('multiple','LA716');
939 sub add_pcr_digest_bands_for_accession {
940 my $self = shift;
941 my ( $bands_string, $stock_name ) = @_;
943 # my $accession_object=CXGN::Accession->new($self->{dbh},$accession);
944 # unless($accession_object)
946 # croak"Accession '$accession' not found\n";
948 # my $accession_id=$accession_object->accession_id();
950 my $stock_id = $self->get_stock_id_with_stock_name($stock_name);
952 unless ($stock_id) { croak("Accession '$stock_name' not found"); }
953 my @bands = split( /\+/, $bands_string );
954 $self->{pcr_digest_bands}->{$stock_id} = \@bands;
955 $self->{pcr_digest_bands} =
956 $self->test_and_clean_bands( $self->{pcr_digest_bands} );
959 sub get_stock_id_with_stock_name {
960 my $self = shift;
961 my $stock_name = shift;
963 my $sth =
964 $self->{dbh}->prepare("SELECT stock_id FROM stock where stock_name=?");
965 $sth->execute($stock_name);
967 my ($stock_id) = $sth->fetchrow_array();
968 return $stock_id;
971 ######################
972 # convenient accessors
973 ######################
975 =head2 pcr_bands_hash_of_strings
977 Get PCR bands in a form that CXGN::Marker::PCR likes.
979 =cut
981 sub pcr_bands_hash_of_strings {
982 my $self = shift;
983 return $self->join_bands_hash( $self->{pcr_bands} );
986 =head2 pcr_digest_bands_hash_of_strings
988 Get PCR digest bands in a form that CXGN::Marker::PCR likes.
990 =cut
992 sub pcr_digest_bands_hash_of_strings {
993 my $self = shift;
994 return $self->join_bands_hash( $self->{pcr_digest_bands} );
997 =head2 test_and_clean_primer
999 For internal use.
1001 =cut
1003 #####
1004 # etc
1005 #####
1006 sub test_and_clean_primer {
1007 my $self = shift;
1008 my ($primer) = @_;
1010 $primer =~ s/\s//g;
1012 unless (
1013 $primer =~ /[ATGCatgc]+/ ) #primers are known base pairs start to finish
1015 croak "'$primer' is not a valid primer";
1017 return uc($primer); #uppercase sequence data
1020 =head2 test_and_clean_bands
1022 For internal use.
1024 =cut
1026 #bands must look like this: {'LA716'=>['Multiple'],'LA925'=>[750,900]}
1027 sub test_and_clean_bands {
1028 my $self = shift;
1029 my ($bands) = @_;
1030 unless ( ref($bands) eq 'HASH' ) {
1031 croak "Bands must be hash ref";
1033 for my $stock_id ( keys( %{$bands} ) ) {
1035 # unless(CXGN::Accession->new($self->{dbh},$accession_id)->accession_id()){croak"Accession '$accession_id' not found";}
1037 unless ( $self->valid_stock_id($stock_id) ) {
1038 croak "stock_id $stock_id is not valid!";
1041 unless ( ref( $bands->{$stock_id} ) eq 'ARRAY' ) {
1042 croak "Bands hash ref must contain array refs";
1044 my @bands_array =
1045 @{ $bands->{$stock_id}
1046 }; #copy this array out to make the following code more readable, maybe
1047 for my $index ( 0 .. $#bands_array ) {
1048 $bands_array[$index] = CXGN::Tools::Text::remove_all_whitespaces(
1049 lc( $bands_array[$index] ) );
1051 if ( $bands_array[$index] =~ /^m/i ) {
1052 $bands_array[$index] = 'Multiple';
1055 unless ( $bands_array[$index] eq 'Multiple' ) {
1056 $bands_array[$index] = int( $bands_array[$index] );
1059 unless ( ( $bands_array[$index] eq 'Multiple' )
1060 or ( CXGN::Tools::Text::is_number( $bands_array[$index] ) ) )
1062 croak "'$bands_array[$index]' is an invalid band size";
1065 $bands->{$stock_id} = \@bands_array; #copy this array back in
1067 return $bands;
1070 sub _valid_stock_id {
1071 my $self = shift;
1072 my $stock_id = shift;
1073 my $sth =
1074 $self->{dbh}->prepare("SELECT stock_id FROM stock WHERE stock_id=?");
1075 $sth->execute($stock_id);
1076 my ($stock_id) = $sth->fetchrow_array();
1077 return $stock_id;
1080 ##store the primers, or any other sequnces linked, in the sequence table, and link to pcr_experiment##
1082 =head2 store_sequence
1084 Usage: $self->store_sequence($sequence_name, $sequence);
1085 Desc: store a primer, or any other sequence type, of the pcr_experiment in the sequence table ,
1086 and link to the experiment using pcr_experiment_sequence table.
1087 Ret: a database id
1088 Args: a string with sequence type, and the sequence string
1089 sequence types should be listed in the cvterm table with cv_name =
1090 'sequence' (this is the namespace for SO http://song.cvs.sourceforge.net/viewvc/song/ontology/so.obo?view=log )
1091 Side Effects: store a new sequence in sgn.sequence, if one does not exist.
1092 Sequences are converted to all upper-case.
1094 Example
1095 my $id = $self->store_sequence('forward_primer','ATCCGTGACGTAC');
1097 =cut
1099 sub store_sequence {
1100 my $self = shift;
1101 my $sequence_type = shift;
1102 my $seq = shift
1103 || die
1104 'No sequence for type $sequence_type passed to store_sequence function! \n';
1106 #find if the type is stored in the database
1107 my $q = "SELECT cvterm_id FROM public.cvterm
1108 WHERE name ilike ? AND cv_id =
1109 (SELECT cv_id FROM public.cv WHERE cv.name ilike ?) ";
1110 my $sth = $self->{dbh}->prepare($q);
1111 $sth->execute( $sequence_type, 'sequence' );
1112 my ($type_id) = $sth->fetchrow_array();
1114 "Sequence type $sequence_type does not exist in the database!\n Expected to find cvterm $sequence_type with cv_name 'sequence'!\n Please check your databae, and make sure Sequence Ontology is up-to-date\n "
1115 if !$type_id;
1117 $seq =~ s/\s//g;
1118 unless ( $seq =~ /[ATGCatgc]+/ ) {
1119 croak "'$seq' is not a valid sequence";
1121 $seq = uc($seq); #uppercase sequence data
1123 my $sql = CXGN::DB::SQLWrappers->new( $self->{dbh} );
1124 my $sequence =
1125 $sql->insert_unless_exists( 'sequence', { 'sequence' => $seq } );
1127 #does the link exist?
1128 my @ids = $sql->select(
1129 'pcr_experiment_sequence',
1131 sequence_id => $sequence->{id},
1132 pcr_experiment_id => $self->{pcr_experiment_id},
1133 type_id => $type_id
1136 if ( !@ids ) {
1138 #store the link
1139 $q =
1140 "Insert INTO sgn.pcr_experiment_sequence (sequence_id, pcr_experiment_id, type_id)
1141 VALUES (?,?,?) RETURNING pcr_experiment_sequence_id";
1142 $sth = $self->{dbh}->prepare($q);
1143 $sth->execute( $sequence->{id}, $self->{pcr_experiment_id}, $type_id );
1144 return ( $sth->fetchrow_array() );
1146 else {
1147 warn("link exists , ids = @ids\n");
1148 return $ids[0];
1151 #my $pcr_seq = $sql->insert_unless_exists('pcr_experiment_sequence' , { 'sequence_id' => $sequence->{id} , 'pcr_experiment_id' => $self->{pcr_experiment_id} , 'type_id' => $type_id } );
1155 ##get the associated sequences and their types from pcr_experiment_sequence##
1157 =head2 get_sequences
1159 Usage: $self->get_sequences
1160 Desc: find the sequences associated with the marker, and their types
1161 Ret: hashref {$sequence_type => [$seq1, $seq2] }
1162 Args: none
1163 Side Effects: none
1165 =cut
1167 sub get_sequences {
1168 my $self = shift;
1169 my $q = "SELECT cvterm.name, sequence FROM sgn.pcr_experiment
1170 JOIN sgn.pcr_experiment_sequence USING (pcr_experiment_id)
1171 JOIN sgn.sequence USING (sequence_id)
1172 JOIN public.cvterm on cvterm_id = sgn.pcr_experiment_sequence.type_id
1173 WHERE pcr_experiment.pcr_experiment_id = ?";
1174 my $sth = $self->{dbh}->prepare($q);
1175 $sth->execute( $self->{pcr_experiment_id} );
1176 my %HoA;
1177 while ( my ( $sequence_type, $sequence ) = $sth->fetchrow_array() ) {
1178 push @{ $HoA{$sequence_type} }, $sequence;
1180 return \%HoA;