modifying Experiment to link to stock instead of individual.
[cxgn-corelibs.git] / lib / CXGN / Marker / PCR / Experiment.pm
blob9f29ea710c7cbef9ed974377b0c9a08dc3826ee6
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();
105 #get pcr products
106 my $sizes;
108 $q = $dbh->prepare(
109 "SELECT stock.stock_id,band_size,multiple_flag FROM sgn.pcr_exp_accession inner join sgn.pcr_product using(pcr_exp_accession_id) inner join public.stock on(pcr_exp_accession.stock_id=stock.stock_id) WHERE enzyme_id is null and pcr_experiment_id=?"
112 $q->execute( $self->{pcr_experiment_id} );
114 $sizes = $q->fetchall_arrayref();
115 if ( $sizes->[0] ) {
116 $self->{pcr_bands} = $self->query_results_to_bands_hash($sizes);
118 $q = $dbh->prepare(
119 "SELECT stock.stock_id,band_size,multiple_flag FROM sgn.pcr_exp_accession inner join sgn.pcr_product using(pcr_exp_accession_id) inner join public.stock on(pcr_exp_accession.stock_id=stock.stock_id) WHERE enzyme_id is not null and pcr_experiment_id=?"
121 $q->execute( $self->{pcr_experiment_id} );
122 $sizes = $q->fetchall_arrayref();
123 if ( $sizes->[0] ) {
124 $self->{pcr_digest_bands} =
125 $self->query_results_to_bands_hash($sizes);
128 #get enzyme
129 $q = $dbh->prepare(
130 "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=?"
132 $q->execute( $self->{pcr_experiment_id} );
133 ( $self->{enzyme_id}, $self->{enzyme} ) = $q->fetchrow_array()
134 ; #only fetching one row, because they all should be the same. there should be both db and api constraints ensuring that.
136 else #else we're creating and empty object
139 #initialize empty object--we want some things to have a default value, so the object it will be consistent
140 #and able to be worked with even if you haven't set its predicted field, for instance
141 $self->{predicted} = 'f';
144 return $self;
147 =head2 pcr_experiment_id
149 my $id=$experiment->pcr_experiment_id();
151 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.
153 =cut
155 #this function cannot be used as a setter, since this id is assigned by the database
156 sub pcr_experiment_id {
157 my $self = shift;
158 return $self->{pcr_experiment_id};
161 =head2 equals
163 my $experiment=CXGN::Marker::PCR::Experiment->new($dbh,$pcr_experiment_id);
164 my $experiment_for_comparison=CXGN::Marker::PCR::Experiment->new($dbh,$possible_match_id);
165 if($experiment->equals($experiment_for_comparison)){print"They are the same!";}
167 =cut
169 ##########################################
170 # compare this pcr experiment with another
171 ##########################################
173 sub check_pcr_band_arrays {
174 my ( $stock, $pcr_hash_1, $pcr_hash_2 ) = @_;
176 my $comp = Array::Compare->new();
178 my $croaking =
179 "PCR bands (or digest bands) entry for stock '$stock' does not appear to be array ref";
181 unless ( $pcr_hash_1->{$stock} and $pcr_hash_2->{$stock} ) { return 0 }
182 unless ( ( ref( $pcr_hash_1->{$stock} ) eq 'ARRAY' ) ) { croak $croaking }
183 unless ( ( ref( $pcr_hash_2->{$stock} ) eq 'ARRAY' ) ) { croak $croaking }
185 # Array::Compare::perm returns true if lists are the same or permutations of each other (bands may have been stored in any order)
186 unless ( $comp->perm( $pcr_hash_1->{$stock}, $pcr_hash_2->{$stock} ) ) {
187 return 0;
190 return 1;
193 sub equals {
194 my $self = shift;
195 my ($other) = @_;
197 unless ( $other->isa('CXGN::Marker::PCR::Experiment') ) {
198 croak "Must send in a PCR experiment object to equals function";
200 unless ( $self->marker_id() and $other->marker_id() ) {
201 croak
202 "Must set both PCR experiment objects' marker IDs before comparing\n-----\nself:\n"
203 . $self->as_string()
204 . "-----\nother:\n"
205 . $other->as_string();
207 unless ( $self->protocol() and $other->protocol() ) {
208 croak
209 "Must set both PCR experiment objects' protocols before comparing";
211 unless ( $self->predicted() eq 'f' or $self->predicted() eq 't' ) {
212 croak "Can't check for equality; invalid predicted field for self:\n"
213 . $self->as_string();
215 unless ( $other->predicted() eq 'f' or $other->predicted() eq 't' ) {
216 croak
217 "Can't check for equality; invalid predicted field for other object:\n"
218 . $other->as_string();
220 if ( $self->marker_id() ne $other->marker_id() ) { return 0 }
221 if ( $self->fwd_primer() ne $other->fwd_primer() ) { return 0 }
222 if ( $self->rev_primer() ne $other->rev_primer() ) { return 0 }
223 if ( $self->primer_type() ne $other->primer_type() ) { return 0 }
224 if ( $self->enzyme() ne $other->enzyme() ) { return 0 }
225 if ( $self->predicted() ne $other->predicted() ) { return 0 }
226 if ( $self->protocol eq 'RFLP' and $other->protocol ne 'RFLP' ) { return 0 }
227 if ( $self->protocol ne 'RFLP' and $other->protocol eq 'RFLP' ) { return 0 }
229 my $pcr_hash_1 = $self->{pcr_bands};
230 my $pcr_hash_2 = $other->{pcr_bands};
232 # remove empty keys
233 for my $k ( keys( %{$pcr_hash_1} ) ) {
234 delete $pcr_hash_1->{$k} if ( @{ $pcr_hash_1->{$k} } == 0 );
236 for my $k ( keys( %{$pcr_hash_2} ) ) {
237 delete $pcr_hash_2->{$k} if ( @{ $pcr_hash_2->{$k} } == 0 );
240 # check pcr band arrays for all stocks in first object
241 for my $stock ( keys( %{$pcr_hash_1} ) ) {
242 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
243 return 0;
247 # then check pcr band arrays for all stocks in second object, in case the second has stocks the first doesn't
248 for my $stock ( keys( %{$pcr_hash_2} ) ) {
249 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
250 return 0;
254 $pcr_hash_1 = $self->{pcr_digest_bands};
255 $pcr_hash_2 = $other->{pcr_digest_bands};
257 # remove empty keys
258 for my $k ( keys( %{$pcr_hash_1} ) ) {
259 delete $pcr_hash_1->{$k} if ( @{ $pcr_hash_1->{$k} } == 0 );
261 for my $k ( keys( %{$pcr_hash_2} ) ) {
262 delete $pcr_hash_2->{$k} if ( @{ $pcr_hash_2->{$k} } == 0 );
265 # check pcr digest band arrays for all stocks in first object
266 for my $stock ( keys( %{$pcr_hash_1} ) ) {
267 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
268 return 0;
272 # then check pcr digest band arrays for all accessions in second object, in case the second has accessions the first doesn't
273 for my $stock ( keys( %{$pcr_hash_2} ) ) {
274 unless ( &check_pcr_band_arrays( $stock, $pcr_hash_1, $pcr_hash_2 ) ) {
275 return 0;
279 #only compare mg and temp IF they are present in BOTH objects... see note below
280 if ( ( $self->mg_conc() and $other->mg_conc() )
281 and ( $self->mg_conc() != $other->mg_conc() ) )
283 return 0;
285 if ( ( $self->temp() and $other->temp() )
286 and ( $self->temp() != $other->temp() ) )
288 return 0;
291 #notes:
292 #we did not compare missing temperature or mg concentration values, because yimin says experiments that are so similar
293 #that the only difference is that one is missing a temp or mg conc are the same experiment
294 #we did not compare additional_enzymes, because this is just a long text notes field, not essential data for the experiment,
295 #and frequently subject to minor changes in its text. this is just feinan's extra COSII PCR data field.
297 return 1;
300 =head2 exists
302 Returns its pcr_experiment_id if it already exists in the database, or undef if not.
304 =cut
306 ###################
307 # storing functions
308 ###################
309 sub exists {
310 my $self = shift;
311 unless ( $self->{marker_id} ) {
312 croak
313 "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";
315 unless ( $self->{protocol} ) {
316 croak
317 "I doubt an experiment like this one exists, since it has no experiment protocol. Set to unknown if necessary.";
319 if ( $self->{pcr_experiment_id} ) {
321 #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}";
322 return $self->{pcr_experiment_id};
324 unless ( $self->predicted() eq 'f' or $self->predicted() eq 't' ) {
325 croak "Can't check for existence; invalid predicted field for self:\n"
326 . $self->as_string();
328 my $possible_matches_query = $self->{dbh}->prepare( "
329 SELECT
330 pcr_experiment_id
331 FROM
332 marker_experiment
333 WHERE
334 marker_id=?
335 and pcr_experiment_id is not null
336 " );
337 $possible_matches_query->execute( $self->marker_id() );
338 while ( my ($possible_match_id) =
339 $possible_matches_query->fetchrow_array() )
342 #print"possible match id: $possible_match_id\n";
343 my $experiment_for_comparison =
344 CXGN::Marker::PCR::Experiment->new( $self->{dbh},
345 $possible_match_id );
346 if ( $self->equals($experiment_for_comparison) ) {
347 $self->{pcr_experiment_id} = $experiment_for_comparison
348 ->{pcr_experiment_id}; #ok, we've been found to already exist, so set our pcr_experiment_id
349 return $self->{pcr_experiment_id};
352 return;
355 =head2 store_unless_exists
357 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.
359 =cut
361 sub store_unless_exists {
362 my $self = shift;
364 if ( $self->exists() ) {
365 print STDERR "This pcr experiment has already been stored.\n";
366 return;
369 unless ( $self->{marker_id} ) {
370 croak "Cannot store experiment without marker ID";
372 unless ( $self->{protocol} ) {
373 croak
374 "Cannot store experiment without protocol. Use 'unknown' if necessary.";
376 unless ( $self->predicted() eq 'f' or $self->predicted() eq 't' ) {
377 croak "Can't store; invalid predicted field for self:\n"
378 . $self->as_string();
380 if ( $self->{pcr_experiment_id} ) {
381 croak
382 "This experiment appears to have been stored already or created from an existing database entry";
384 ##################### TODO #########################
385 #if we already have a PCR experiment ID, and someone
386 #calls 'store_unless_exists', this is a perfectly
387 #reasonable use case, but i have not implemented it yet.
388 #they might want to modify an existing experiment. for
389 #instance, it is common that someone might add digested
390 #bands later, after having loaded an experiment with
391 #only regular pcr bands a few months before. this object cannot yet handle this
392 #situation. that is why it croaks here. if you need to add
393 #this functionality, add it here. it would consist of some
394 #kind of object integrity checking and checking for values
395 #which have been added or modified and adding or modifying
396 #those same values in the database. alternatively, you may
397 #just want to write another class--CXGN::Marker::PCR::Experiment::Modfiable
398 #or something like that which has fewer checks and just
399 #directly accesses data in the database using an object
400 #like lukas's modifiable form object.
401 if ( $self->{pcr_digest_bands} ) {
402 unless ( $self->{enzyme_id} ) {
403 croak "Must have an enzyme set to store digest bands";
407 my $dbh = $self->{dbh};
408 my $sql = CXGN::DB::SQLWrappers->new( $self->{dbh} );
410 if ( $self->fwd_primer() ) {
411 my $fwd_info =
412 $sql->insert_unless_exists( 'sequence',
413 { 'sequence' => $self->fwd_primer() } );
414 $self->{fwd_primer_id} = $fwd_info->{id};
416 if ( $self->rev_primer() ) {
417 my $rev_info =
418 $sql->insert_unless_exists( 'sequence',
419 { 'sequence' => $self->rev_primer() } );
420 $self->{rev_primer_id} = $rev_info->{id};
423 #print"INSERTING:\n".$self->as_string();
425 my $pcr_exp_insert = $self->{dbh}->prepare( '
426 insert into sgn.pcr_experiment (
427 mg_concentration,
428 annealing_temp,
429 primer_id_fwd,
430 primer_id_rev,
431 primer_type,
432 additional_enzymes,
433 predicted
435 values (?,?,?,?,?,?,?)
436 ' );
437 $pcr_exp_insert->execute(
438 $self->{mg_concentration}, $self->{annealing_temp},
439 $self->{fwd_primer_id}, $self->{rev_primer_id},
440 $self->{primer_type}, $self->{additional_enzymes},
441 $self->{predicted}
443 $self->{pcr_experiment_id} = $self->{dbh}->last_insert_id('pcr_experiment')
444 or croak "Could not get last_insert_id from pcr_experiment";
446 my %stocks;
447 for my $stock (
448 keys( %{ $self->{pcr_bands} } ),
449 keys( %{ $self->{pcr_digest_bands} } )
452 $stocks{$stock} = 0;
455 # dummy value for now, until we get a pcr_exp_accession_id
457 my $exp_acc_insert =
458 $self->{dbh}->prepare(
459 'insert into sgn.pcr_exp_accession (pcr_experiment_id,stock_id) values (?,?)'
461 my $pcr_band_insert =
462 $self->{dbh}->prepare(
463 'insert into sgn.pcr_product (pcr_exp_accession_id,enzyme_id,multiple_flag,band_size,predicted) values (?,?,?,?,?)'
468 for my $stock_id ( keys(%stocks) ) {
469 $exp_acc_insert->execute( $self->{pcr_experiment_id}, $stock_id );
470 $stocks{$stock_id} = $self->{dbh}->last_insert_id('pcr_exp_accession')
471 or croak "Could not get last_insert_id from pcr_exp_accession";
473 my @stock_pcr_bands;
474 my @stock_pcr_digest_bands;
475 if ( $self->{pcr_bands}->{$stock_id} ) {
476 @stock_pcr_bands = @{ $self->{pcr_bands}->{$stock_id} };
478 if ( $self->{pcr_digest_bands}->{$stock_id} ) {
479 @stock_pcr_digest_bands =
480 @{ $self->{pcr_digest_bands}->{$stock_id} };
482 if ( $stock_pcr_bands[0] ) { #if there is at least one value in the pcr bands list for this stock
483 for my $band (@stock_pcr_bands) {
485 #if the band entry starts with an m, it means multiple bands, so set the multiple flag. no enzyme insert for regular pcr bands.
486 if ( $band =~ /^m/i ) {
487 $pcr_band_insert->execute( $stocks{$stock_id}, undef, 1,
488 undef, $self->{predicted} );
490 else {
492 $pcr_band_insert->execute( $stocks{$stock_id}, undef, undef,
493 $band, $self->{predicted} );
497 if ( $stock_pcr_digest_bands[0] ) {
498 # if there is at least one value in the pcr digest bands list for this stock
499 #if the band entry starts with an m, it means multiple bands, so set the multiple flag.
500 for my $band (@stock_pcr_digest_bands) {
501 if ( $band =~ /^m/i ) {
502 $pcr_band_insert->execute( $stocks{$stock_id},
503 $self->{enzyme_id}, 1, undef, $self->{predicted} );
505 else {
506 $pcr_band_insert->execute( $stocks{$stock_id},
507 $self->{enzyme_id}, undef, $band, $self->{predicted} );
513 #and now for a final test of this object
514 if ( my $oops_id = $self->store_unless_exists() ) {
515 my $croaking =
516 "Oops, this object isn't working correctly. Immediately after being stored with ID "
517 . "'$self->{pcr_experiment_id}', it tried to store itself again as a test, and succeeded with ID '$oops_id' "
518 . "(it should have failed, because it was already inserted!)";
519 croak $croaking;
522 return $self->{pcr_experiment_id};
525 =head2 update_additional_enzymes
527 #this will actually update the pcr experiment entry in the database
528 $experiment->update_additional_enzymes('All possible enzymes for blah blah blah are blah blah blah....');
530 =cut
532 #storing function for additional_enzymes field. this data is not essential to the experiment. it is just a text field with
533 #notes that feinan wants to show up for cosii markers, so it has no special checks or anything.
534 sub update_additional_enzymes {
535 my $self = shift;
536 my ($additional_enzymes) = @_;
537 if ( length($additional_enzymes) > 1023 ) {
538 croak
539 "Additional enzymes field contents size limit is exceeded by string '$additional_enzymes'";
541 unless ( $self->{pcr_experiment_id} ) {
542 croak
543 "This experiment object does not appear to have been loaded or inserted into the database yet, so you cannot update its enzymes";
545 my $sth =
546 $self->{dbh}->prepare(
547 'update pcr_experiment set additional_enzymes=? where pcr_experiment_id=?'
549 $sth->execute( $additional_enzymes, $self->{pcr_experiment_id} );
552 =head2 as_string
554 #print out the whole experiment, for debugging, or for loading script output
555 print $experiment->as_string();
557 =cut
559 #######################
560 # display for debugging
561 #######################
562 sub as_string {
563 my $self = shift;
564 my $string = "";
565 $string .= "<pcr_experiment>\n";
566 my @marker_names;
567 if ( $self->{marker_id} ) {
568 @marker_names =
569 CXGN::Marker->new( $self->{dbh}, $self->{marker_id} )
570 ->name_that_marker();
572 else {
573 @marker_names = ('<no marker associated yet>');
575 $string .= "\tMarker: @marker_names\n";
576 $string .= "\tPCR experiment ID: $self->{pcr_experiment_id}\n";
577 if ( $self->{location_id} ) {
578 $string .=
579 "\tThis is a mapping experiment; location ID: $self->{location_id}\n";
581 else {
582 $string .=
583 "\tThis experiment does not yet have a map location associated with it in the marker_experiment table\n";
585 $string .= "\tProtocol: $self->{protocol}\n";
586 $string .=
587 "\tPrimers: $self->{fwd_primer} (fwd)\t\t$self->{rev_primer} (rev)\n";
588 my $pt = $self->{primer_type};
589 $pt ||= '';
590 $string .= "\tPrimer type: $pt\n";
591 my $mg = $self->{mg_concentration};
592 $mg ||= '';
593 my $temp = $self->{annealing_temp};
594 $temp ||= '';
595 $string .= "\tConditions: $mg MG - $temp C\n";
596 if ( $self->{enzyme} ) { $string .= "\tEnzyme: $self->{enzyme}\n"; }
597 $string .= "\tBands:\n";
598 my $bands = $self->pcr_bands_hash_of_strings();
600 if ( $bands and %{$bands} ) {
601 for my $stock ( keys( %{$bands} ) ) {
603 #$string.="\t".CXGN::Accession->new($self->{dbh},$accession)->extra_verbose_name().": ".$bands->{$accession}."\n";
604 $string .= "\t$stock : " . $bands->{$stock} . "\n";
607 $string .= "\tDigest bands:\n";
608 $bands = $self->pcr_digest_bands_hash_of_strings();
609 if ( $bands and %{$bands} ) {
610 for my $stock ( keys( %{$bands} ) ) {
611 $string .= "\t$stock : " . $bands->{$stock} . "\n";
614 $string .= "\tPredicted: $self->{predicted}\n";
615 $string .= "</pcr_experiment>\n";
618 =head2 query_results_to_bands_hash
620 For internal use. Converts bands query results into a form that can be stored easily.
622 =cut
624 ###########################################
625 # helpful functions mainly for internal use
626 ###########################################
627 sub query_results_to_bands_hash {
628 my $self = shift;
629 my ($sizes) = @_;
630 my %bands;
631 for my $row ( @{$sizes} ) {
632 my ( $stock, $band_size, $multiple_flag ) = @{$row};
633 if ( $stock and ( $band_size or $multiple_flag ) ) {
634 my $insert_value;
635 if ($band_size) {
636 $insert_value = $band_size;
638 elsif ($multiple_flag) {
639 $insert_value = 'Multiple';
641 push( @{ $bands{$stock} }, $insert_value );
643 else {
644 croak "Unable to load bands hash";
647 return \%bands;
650 =head2 join_bands_hash
652 For internal use. Converts bands into a more useful form.
654 =cut
656 sub join_bands_hash {
657 my $self = shift;
658 my ($bands_hash) = @_;
659 my %expected_structure;
660 for my $stock ( keys( %{$bands_hash} ) ) {
661 $expected_structure{$stock} = join( '+', @{ $bands_hash->{$stock} } );
663 if ( keys(%expected_structure) ) #if there are values to return, return them
665 return \%expected_structure;
667 else {
668 return;
672 =head2 marker_id
674 my $id=$experiment->marker_id();
676 Gets the marker_id of marker which is involved in this experiment.
678 $experiment->marker_id($marker_id);
680 Sets the marker_id of marker which is involved in this experiment.
682 =cut
684 ###################
685 # accessors/setters
686 ###################
687 sub marker_id {
688 my $self = shift;
689 my ($value) = @_;
690 if ($value) {
691 unless ( $value =~ /^\d+$/ ) {
692 croak "Marker ID must be a number, not '$value'";
694 unless (
695 CXGN::Marker::Tools::is_valid_marker_id( $self->{dbh}, $value ) )
697 croak "Marker ID '$value' does not exist in the database";
699 $self->{marker_id} = $value;
701 return $self->{marker_id};
704 =head2 fwd_primer
706 Returns or sets the forward primer.
708 =cut
710 sub fwd_primer {
711 my $self = shift;
712 my ($value) = @_;
713 if ($value) {
714 $value = $self->test_and_clean_primer($value);
715 $self->{fwd_primer} = $value;
717 return $self->{fwd_primer};
720 =head2 rev_primer
722 Returns or sets the reverse primer.
724 =cut
726 sub rev_primer {
727 my $self = shift;
728 my ($value) = @_;
729 if ($value) {
730 $value = $self->test_and_clean_primer($value);
731 $self->{rev_primer} = $value;
733 return $self->{rev_primer};
736 sub dcaps_primer {
737 my $self = shift;
738 my ($value) = @_;
739 if ($value) {
740 $value = $self->test_and_clean_primer($value);
741 $self->{dcaps_primer} = $value;
743 return $self->{dcaps_primer};
746 =head2 primer_type
748 Returns or sets the primer type.
750 =cut
752 sub primer_type {
753 my $self = shift;
754 my ($value) = @_;
755 if ($value) {
756 unless ( $value eq 'iUPA' or $value eq 'eUPA' ) {
757 croak "'$value' is not a valid primer type";
759 $self->{primer_type} = $value;
761 return $self->{primer_type};
764 =head2 mg_conc
766 Returns or sets the magnesium concentration.
768 =cut
770 sub mg_conc {
771 my $self = shift;
772 my ($value) = @_;
773 if ($value) {
774 unless ( CXGN::Tools::Text::is_number($value) ) {
775 croak "'$value' is not a valid number for mg concentration";
777 $self->{mg_concentration} = $value;
779 return $self->{mg_concentration};
782 =head2 temp
784 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.
786 =cut
788 sub temp {
789 my $self = shift;
790 my ($value) = @_;
791 if ($value) {
792 unless ( $value =~ /^(\d*?\.?\d*?)[cf]?$/i ) {
793 croak "'$value' is an invalid anneal temp";
795 $value =~ s/C$//i; #strip C for Celsius
796 if ( $value =~
797 s/F$//i ) #if it was an F for Fahrenheit, convert it to Celsius
799 $value = ( $value + 40 ) * 5 / 9;
801 $self->{annealing_temp} = $value;
803 return $self->{annealing_temp};
806 =head2 protocol
808 Returns or sets the experiment protocol.
810 =cut
812 sub protocol {
813 my $self = shift;
814 my ($protocol) = @_;
815 if ($protocol) {
816 unless ( $protocol eq 'AFLP'
817 or $protocol eq 'CAPS'
818 or $protocol eq 'RAPD'
819 or $protocol eq 'SNP'
820 or $protocol eq 'SSR'
821 or $protocol eq 'RFLP'
822 or $protocol eq 'PCR'
823 or $protocol eq 'unknown'
824 or $protocol =~ /Indel/i )
826 croak "Protocol '$protocol' is invalid.";
828 if ( $protocol eq 'RFLP' ) {
829 croak "RFLP is not a valid PCR experiment protocol";
831 $self->{protocol} = $protocol;
833 return $self->{protocol};
836 =head2 enzyme
838 Returns or sets the enzyme used to get the digest bands.
840 =cut
842 sub enzyme {
843 my $self = shift;
844 my ($enzyme) = @_;
845 if ($enzyme) {
846 $enzyme =~ s/\s//g unless $enzyme =~ /and/; #clear whitespace
847 $enzyme =~ s/(1+)$/'I' x length($1)/e; #1 -> I
849 # this isn't working for some reason
851 if ( $enzyme eq 'PCR' ) { $enzyme = 'amplicon difference' }
853 # TODO: change this to undef once everything is working
854 elsif ( $enzyme eq 'SNP' ) { $enzyme = 'unknown' }
855 elsif ( !$enzyme ) { $enzyme = 'unknown' }
857 unless ( $self->{enzyme_id} =
858 CXGN::Marker::Tools::get_enzyme_id( $self->{dbh}, $enzyme ) )
860 croak
861 "'$enzyme' is not a valid enzyme (you may need to add it to the enzyme table)";
864 $self->{enzyme} = $enzyme;
867 return $self->{enzyme};
870 =head2 additional_enzymes
872 Returns or sets Feinan^s COSII additional_enzymes field.
874 =cut
876 sub additional_enzymes {
877 my $self = shift;
878 my ($value) = @_;
879 if ($value) { $self->{additional_enzymes} = $value; }
880 return $self->{additional_enzymes};
883 =head2 predicted
885 Returns or sets whether or not the band sizes stored in this object are predicted.
887 =cut
889 sub predicted {
890 my $self = shift;
891 my ($value) = @_;
892 if ($value) {
893 $value = lc($value);
894 unless ( $value eq 't' or $value eq 'f' ) {
895 croak "Predicted must be either 't' or 'f'";
897 $self->{predicted} = $value;
899 return $self->{predicted};
902 =head2 add_pcr_bands_for_stock
904 $experiment->add_pcr_bands_for_stock('250+400','LA716');
906 In a change from the previous version, this actually hard stores
907 the band info in the database. It only inserts a band size once.
909 Note: add_pcr_bands_for_accession is deprecated.
911 =cut
913 #example use: $created_experiment->add_pcr_bands_string_for_accession('750+900','LA925');
915 sub add_pcr_bands_for_accession {
916 warn "add_pcr_bands_for_accession is deprecated\n";
917 shift->add_pcr_bands_for_stock(@_);
920 sub add_pcr_bands_for_stock {
921 my $self = shift;
922 my ( $bands_string, $stock_name ) = @_;
924 # my $accession_object=CXGN::Accession->new($self->{dbh},$accession);
925 # unless($accession_object)
927 # croak"Accession '$stock_name' not found\n";
930 if (!$self->{pcr_experiment_id}) {
931 die "Need a pcr_experiment_id!!!\n";
934 my $stock_id = $self->get_stock_id_with_stock_name($stock_name);
936 #my $accession_id=$accession_object->accession_id();
937 unless ($stock_id) { croak("Accession '$stock_name' not found"); }
938 my @bands = split( /\+/, $bands_string );
939 $self->{pcr_bands}->{$stock_id} = \@bands;
940 $self->{pcr_bands} = $self->test_and_clean_bands( $self->{pcr_bands} );
942 # print STDERR "ADDING BANDS ".(@bands)."\n";
943 foreach my $band (@bands) {
944 my $sth = $self->{dbh}->prepare("SELECT marker_id, pcr_exp_accession.pcr_exp_accession_id, pcr_product_id FROM pcr_experiment JOIN pcr_exp_accession USING(pcr_experiment_id) LEFT JOIN pcr_product USING (pcr_exp_accession_id) WHERE band_size=? AND pcr_exp_accession.stock_id=? AND marker_id=?");
945 $sth->execute($band, $stock_id, $self->marker_id());
946 my ($marker_id, $pcr_exp_accession_id, $pcr_product_id) = $sth->fetchrow_array();
948 if ( $pcr_exp_accession_id) {
949 print STDERR "pcr_exp_accession for $stock_id is already in the database -- not storing\n";
951 else {
952 my $p1 = $self->{dbh}->prepare("INSERT INTO sgn.pcr_exp_accession (pcr_experiment_id, stock_id) VALUES (?, ?) RETURNING pcr_exp_accession_id");
953 $p1->execute($self->{pcr_experiment_id}, $stock_id);
954 ($pcr_exp_accession_id) = $p1->fetchrow_array();
959 if ($pcr_product_id ) {
960 print STDERR "pcr_product of size $band for stock $stock_id already in database -- not storing.\n";
962 else {
963 my $new_pcr_product_id = $self->{dbh}->do("INSERT INTO sgn.pcr_product (pcr_exp_accession_id, band_size) VALUES (?, ?) RETURNING pcr_product_id", {}, $pcr_exp_accession_id, $band);
971 =head2 add_pcr_digest_bands_for_stock
973 $experiment->add_pcr_digest_bands_for_stock('250+400','LA716');
975 (needs fixing, see add_digest_bands_for_stock)
977 Side effect: Dies if the stock is not valid.
978 Note: add_pcr_digest_bands_for_accession is deprecated.
980 =cut
982 sub add_pcr_bands_for_accession {
983 warn "add_pcr_digest_bands_for_accession is deprecated.\n";
984 shift->add_pcr_digest_bands_for_stock(@_);
987 #example use: $created_experiment->add_pcr_digest_bands_string_for_accession('multiple','LA716');
988 sub add_pcr_digest_bands_for_accession {
989 my $self = shift;
990 my ( $bands_string, $stock_name ) = @_;
992 # my $accession_object=CXGN::Accession->new($self->{dbh},$accession);
993 # unless($accession_object)
995 # croak"Accession '$accession' not found\n";
997 # my $accession_id=$accession_object->accession_id();
999 my $stock_id = $self->get_stock_id_with_stock_name($stock_name);
1001 unless ($stock_id) { croak("Accession '$stock_name' not found"); }
1002 my @bands = split( /\+/, $bands_string );
1003 $self->{pcr_digest_bands}->{$stock_id} = \@bands;
1004 $self->{pcr_digest_bands} =
1005 $self->test_and_clean_bands( $self->{pcr_digest_bands} );
1008 sub get_stock_id_with_stock_name {
1009 my $self = shift;
1010 my $stock_name = shift;
1012 my $sth =
1013 $self->{dbh}->prepare("SELECT stock_id FROM stock where name=?");
1014 $sth->execute($stock_name);
1016 my ($stock_id) = $sth->fetchrow_array();
1017 return $stock_id;
1020 ######################
1021 # convenient accessors
1022 ######################
1024 =head2 pcr_bands_hash_of_strings
1026 Get PCR bands in a form that CXGN::Marker::PCR likes.
1028 =cut
1030 sub pcr_bands_hash_of_strings {
1031 my $self = shift;
1032 return $self->join_bands_hash( $self->{pcr_bands} );
1035 =head2 pcr_digest_bands_hash_of_strings
1037 Get PCR digest bands in a form that CXGN::Marker::PCR likes.
1039 =cut
1041 sub pcr_digest_bands_hash_of_strings {
1042 my $self = shift;
1043 return $self->join_bands_hash( $self->{pcr_digest_bands} );
1046 =head2 test_and_clean_primer
1048 For internal use.
1050 =cut
1052 #####
1053 # etc
1054 #####
1055 sub test_and_clean_primer {
1056 my $self = shift;
1057 my ($primer) = @_;
1059 $primer =~ s/\s//g;
1061 unless (
1062 $primer =~ /[ATGCatgc]+/ ) #primers are known base pairs start to finish
1064 croak "'$primer' is not a valid primer";
1066 return uc($primer); #uppercase sequence data
1069 =head2 test_and_clean_bands
1071 For internal use.
1073 =cut
1075 #bands must look like this: {'LA716'=>['Multiple'],'LA925'=>[750,900]}
1076 sub test_and_clean_bands {
1077 my $self = shift;
1078 my ($bands) = @_;
1079 unless ( ref($bands) eq 'HASH' ) {
1080 croak "Bands must be hash ref";
1082 for my $stock_id ( keys( %{$bands} ) ) {
1084 # unless(CXGN::Accession->new($self->{dbh},$accession_id)->accession_id()){croak"Accession '$accession_id' not found";}
1086 unless ( $self->_valid_stock_id($stock_id) ) {
1087 croak "stock_id $stock_id is not valid!";
1090 unless ( ref( $bands->{$stock_id} ) eq 'ARRAY' ) {
1091 croak "Bands hash ref must contain array refs";
1093 my @bands_array =
1094 @{ $bands->{$stock_id}
1095 }; #copy this array out to make the following code more readable, maybe
1096 for my $index ( 0 .. $#bands_array ) {
1097 $bands_array[$index] = CXGN::Tools::Text::remove_all_whitespaces(
1098 lc( $bands_array[$index] ) );
1100 if ( $bands_array[$index] =~ /^m/i ) {
1101 $bands_array[$index] = 'Multiple';
1104 unless ( $bands_array[$index] eq 'Multiple' ) {
1105 $bands_array[$index] = int( $bands_array[$index] );
1108 unless ( ( $bands_array[$index] eq 'Multiple' )
1109 or ( CXGN::Tools::Text::is_number( $bands_array[$index] ) ) )
1111 croak "'$bands_array[$index]' is an invalid band size";
1114 $bands->{$stock_id} = \@bands_array; #copy this array back in
1116 return $bands;
1119 sub _valid_stock_id {
1120 my $self = shift;
1121 my $stock_id = shift;
1122 my $sth =
1123 $self->{dbh}->prepare("SELECT stock_id FROM stock WHERE stock_id=?");
1124 $sth->execute($stock_id);
1125 my ($stock_id) = $sth->fetchrow_array();
1126 return $stock_id;
1129 ##store the primers, or any other sequnces linked, in the sequence table, and link to pcr_experiment##
1131 =head2 store_sequence
1133 Usage: $self->store_sequence($sequence_name, $sequence);
1134 Desc: store a primer, or any other sequence type, of the pcr_experiment in the sequence table ,
1135 and link to the experiment using pcr_experiment_sequence table.
1136 Ret: a database id
1137 Args: a string with sequence type, and the sequence string
1138 sequence types should be listed in the cvterm table with cv_name =
1139 'sequence' (this is the namespace for SO http://song.cvs.sourceforge.net/viewvc/song/ontology/so.obo?view=log )
1140 Side Effects: store a new sequence in sgn.sequence, if one does not exist.
1141 Sequences are converted to all upper-case.
1143 Example
1144 my $id = $self->store_sequence('forward_primer','ATCCGTGACGTAC');
1146 =cut
1148 sub store_sequence {
1149 my $self = shift;
1150 my $sequence_type = shift;
1151 my $seq = shift
1152 || die
1153 'No sequence for type $sequence_type passed to store_sequence function! \n';
1155 #find if the type is stored in the database
1156 my $q = "SELECT cvterm_id FROM public.cvterm
1157 WHERE name ilike ? AND cv_id =
1158 (SELECT cv_id FROM public.cv WHERE cv.name ilike ?) ";
1159 my $sth = $self->{dbh}->prepare($q);
1160 $sth->execute( $sequence_type, 'sequence' );
1161 my ($type_id) = $sth->fetchrow_array();
1163 "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 "
1164 if !$type_id;
1166 $seq =~ s/\s//g;
1167 unless ( $seq =~ /[ATGCatgc]+/ ) {
1168 croak "'$seq' is not a valid sequence";
1170 $seq = uc($seq); #uppercase sequence data
1172 my $sql = CXGN::DB::SQLWrappers->new( $self->{dbh} );
1173 my $sequence =
1174 $sql->insert_unless_exists( 'sequence', { 'sequence' => $seq } );
1176 #does the link exist?
1177 my @ids = $sql->select(
1178 'pcr_experiment_sequence',
1180 sequence_id => $sequence->{id},
1181 pcr_experiment_id => $self->{pcr_experiment_id},
1182 type_id => $type_id
1185 if ( !@ids ) {
1187 #store the link
1188 $q =
1189 "Insert INTO sgn.pcr_experiment_sequence (sequence_id, pcr_experiment_id, type_id)
1190 VALUES (?,?,?) RETURNING pcr_experiment_sequence_id";
1191 $sth = $self->{dbh}->prepare($q);
1192 $sth->execute( $sequence->{id}, $self->{pcr_experiment_id}, $type_id );
1193 return ( $sth->fetchrow_array() );
1195 else {
1196 warn("link exists , ids = @ids\n");
1197 return $ids[0];
1200 #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 } );
1204 ##get the associated sequences and their types from pcr_experiment_sequence##
1206 =head2 get_sequences
1208 Usage: $self->get_sequences
1209 Desc: find the sequences associated with the marker, and their types
1210 Ret: hashref {$sequence_type => [$seq1, $seq2] }
1211 Args: none
1212 Side Effects: none
1214 =cut
1216 sub get_sequences {
1217 my $self = shift;
1218 my $q = "SELECT cvterm.name, sequence FROM sgn.pcr_experiment
1219 JOIN sgn.pcr_experiment_sequence USING (pcr_experiment_id)
1220 JOIN sgn.sequence USING (sequence_id)
1221 JOIN public.cvterm on cvterm_id = sgn.pcr_experiment_sequence.type_id
1222 WHERE pcr_experiment.pcr_experiment_id = ?";
1223 my $sth = $self->{dbh}->prepare($q);
1224 $sth->execute( $self->{pcr_experiment_id} );
1225 my %HoA;
1226 while ( my ( $sequence_type, $sequence ) = $sth->fetchrow_array() ) {
1227 push @{ $HoA{$sequence_type} }, $sequence;
1229 return \%HoA;