Skip to content

Commit 7067c0c

Browse files
author
Shaun Mahony
committed
Merge branch 'claude/inspiring-mccarthy': STAMP v2.0 web platform
Merges 18 commits adding the STAMP v2.0 Next.js web platform including: - Next.js 14 app with BullMQ/Redis job queue and MongoDB storage - Multi-format motif input (TRANSFAC, MEME, JASPAR, TF-MoDISco, consensus, FASTA) - Database matching against JASPAR, CIS-BP, HOCOMOCO v14, Vierstra archetypes - Interactive results: D3 sequence logos, tree viewer, alignment viewers - Admin dashboard for reference database management and sync - Security hardening: sessions, rate limiting, CSRF, XSS prevention, path injection fixes - Docker Compose deployment with multi-stage builds - README v2.0 documentation and updated site header
2 parents 525db50 + e3f9930 commit 7067c0c

95 files changed

Lines changed: 20980 additions & 110 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
stamp
1+
/stamp
22
outFile*
33
Website
44
.gitignore

README.md

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# STAMP v.1.3.
1+
# STAMP v2.0
22

3-
[STAMP](http://www.benoslab.pitt.edu/stamp/) is a tool for characterizing similarities between transcription factor binding motifs.
3+
[STAMP](https://github.com/seqcode/stamp) is a tool for characterizing similarities between transcription factor binding motifs. STAMP includes both a command-line tool for motif comparison and a web platform for interactive analysis, visualization, and database matching.
44

5-
You can also use the formatMotifs.pl script to convert the outputs from various motif-finders into the format used by STAMP.
5+
You can also use the formatMotifs.pl script to convert the outputs from various motif-finders into the format used by STAMP.
66

77

88
## Building STAMP:
@@ -119,8 +119,93 @@ the **formatMotifs.pl** script.
119119
[List of input formats](http://www.benoslab.pitt.edu/stamp/help.html#input):
120120

121121

122+
## Web Server
123+
124+
STAMP v2.0 includes a web platform (in the `web/` directory) that wraps the STAMP command-line tool in an interactive application. Users can submit motif analysis jobs, visualize results, and match motifs against reference databases — all through a browser.
125+
126+
### Quick Start (Docker)
127+
128+
The easiest way to run the web platform is with Docker Compose:
129+
130+
```bash
131+
cd web/docker
132+
docker compose up
133+
```
134+
135+
This starts the web server (port 3000), a background worker, MongoDB, and Redis. Copy `web/.env.example` to `web/.env.local` and configure as needed before starting.
136+
137+
### Quick Start (Development)
138+
139+
Prerequisites: Node.js >= 20, MongoDB, Redis, and the compiled STAMP binary.
140+
141+
```bash
142+
cd web
143+
cp .env.example .env.local # edit with your local paths
144+
npm install
145+
npm run dev # Next.js dev server on port 3000
146+
npm run worker:dev # background job processor (separate terminal)
147+
```
148+
149+
### Architecture Overview
150+
151+
The web platform is built with:
152+
153+
* **Next.js 14** (App Router) — serves both the React frontend and REST API routes
154+
* **MongoDB** (via Mongoose) — stores jobs, results, and reference database metadata
155+
* **Redis + BullMQ** — asynchronous job queue for running STAMP analyses
156+
* **Worker process** — picks jobs from the queue, invokes the STAMP binary, parses output, and stores results
157+
158+
**Motif input** supports six formats: TRANSFAC, MEME, JASPAR, TF-MoDISco, consensus, and aligned FASTA. Formats are auto-detected on upload.
159+
160+
**Database matching** compares input motifs against reference databases synced from JASPAR, CIS-BP, HOCOMOCO v14, and Vierstra motif archetypes. An admin dashboard manages database synchronization.
161+
162+
**Results** include interactive D3-based sequence logos, a phylogenetic tree viewer, pairwise and multiple alignment viewers, and database match tables. Results can be downloaded as a self-contained HTML report or a ZIP archive.
163+
164+
### Directory Structure
165+
166+
```
167+
web/
168+
├── src/
169+
│ ├── app/ # Next.js pages and API routes
170+
│ │ ├── api/ # REST endpoints (jobs, admin, databases, SSE)
171+
│ │ ├── admin/ # Admin dashboard
172+
│ │ └── jobs/[jobId]/ # Results page
173+
│ ├── components/ # React components
174+
│ │ ├── motif/ # Sequence logos, motif input
175+
│ │ ├── results/ # Tree, alignment, match viewers
176+
│ │ └── job/ # Parameter form, database selector
177+
│ ├── lib/ # Server-side logic
178+
│ │ ├── motif/ # Format parsers and converters
179+
│ │ ├── stamp/ # STAMP binary runner and output parser
180+
│ │ ├── db/ # Mongoose models (Job, ReferenceDatabase)
181+
│ │ ├── queue/ # BullMQ queue setup
182+
│ │ ├── auth/ # Session management, rate limiting
183+
│ │ ├── export/ # HTML report and logo rendering
184+
│ │ └── jaspar/ # Reference database sync clients
185+
│ │ cisbp/
186+
│ │ hocomoco/
187+
│ │ vierstra/
188+
│ └── types/ # TypeScript type definitions
189+
├── worker/ # Background job processor
190+
└── docker/ # Dockerfile and docker-compose configs
191+
```
192+
193+
122194
## Version history:
123195

196+
* **v2.0:** 2026-03-03:
197+
* Added a web platform for interactive motif analysis (`web/` directory).
198+
* Multi-format motif input with auto-detection (TRANSFAC, MEME, JASPAR, TF-MoDISco, consensus, aligned FASTA).
199+
* Interactive D3-based sequence logo visualization with export options.
200+
* Asynchronous job processing via BullMQ/Redis queue with real-time progress updates (SSE).
201+
* Database matching against JASPAR, CIS-BP, HOCOMOCO v14, and Vierstra motif archetypes.
202+
* Admin dashboard for syncing and managing reference databases.
203+
* Results page with phylogenetic tree viewer, pairwise/multiple alignment viewers, and match tables.
204+
* Downloadable self-contained HTML report and ZIP archive of results.
205+
* Optional email notifications on job completion.
206+
* Docker Compose deployment with multi-stage builds.
207+
* Security hardening: Redis-backed sessions, rate limiting, CSRF protection, XSS prevention.
208+
124209
* **v.1.3:** 2016-09-23:
125210
* Make parsing of STAMP specific TRANSFAC file more robust.
126211
Before this change STAMP only processes TRANSFAC files
@@ -160,9 +245,7 @@ the **formatMotifs.pl** script.
160245

161246
## Contact details:
162247

163-
* Personal website: http://mahonylab.org/
164-
* STAMP website: http://www.benoslab.pitt.edu/stamp/
165-
* Download STAMP:
166-
* https://github.com/seqcode/stamp
167-
* http://www.csb.pitt.edu/Faculty/benos/?page_id=51
248+
* Mahony Lab: https://mahonylab.org/
249+
* GitHub: https://github.com/seqcode/stamp
250+
* Legacy STAMP website: http://www.benoslab.pitt.edu/stamp/
168251

src/Alignment.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,11 @@ MultiAlignRec::MultiAlignRec(int nA, int aL)
8282
alignL=aL;
8383
alignedNames = new char*[numAligned];
8484
alignedIDs = new int [numAligned];
85-
for(i=0; i<numAligned; i++)
85+
alignedRC = new bool[numAligned];
86+
for(i=0; i<numAligned; i++){
8687
alignedNames[i] = new char[STR_LEN];
88+
alignedRC[i] = false;
89+
}
8790
profileAlignment = new Motif*[numAligned];
8891
for(i=0; i<numAligned; i++){
8992
profileAlignment[i]=new Motif(alignL);

src/Alignment.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class MultiAlignRec{
101101
Motif** profileAlignment;
102102
char** alignedNames;
103103
int* alignedIDs;
104+
bool* alignedRC; // Tracks whether each motif was reverse-complemented relative to input
104105

105106
//Constructor
106107
MultiAlignRec(int nA=2, int aL=0);
@@ -118,6 +119,7 @@ class MultiAlignRec{
118119
delete [] alignedNames[k];
119120
delete [] alignedNames;
120121
delete [] alignedIDs;
122+
delete [] alignedRC;
121123
}
122124
};
123125

src/MultipleAlignment.cpp

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ void MultipleAlignment::PrintMultipleAlignmentConsensus(MultiAlignRec* alignment
160160
{
161161
alignment = completeAlignment;
162162
if(alignment == NULL)
163-
{ printf("Error: complete alignment not yet constructed!\n\n");
163+
{ fprintf(stderr, "Error: complete alignment not yet constructed!\n\n");
164164
exit(1);
165165
}
166166
}
@@ -170,6 +170,8 @@ void MultipleAlignment::PrintMultipleAlignmentConsensus(MultiAlignRec* alignment
170170
int aL = alignment->GetAlignL();
171171

172172
if(aL>0){
173+
if(webMode)
174+
printf(">>STAMP_MULTI_ALIGN_CONSENSUS_START\n");
173175
if(htmlOutput)
174176
printf("<center><font face=\"Courier New\"><table border=\"0\" width=\"700\">");
175177
for(int q=0; q<alignment->GetNumAligned(); q++){
@@ -195,7 +197,8 @@ void MultipleAlignment::PrintMultipleAlignmentConsensus(MultiAlignRec* alignment
195197
printf("\n");
196198
if(htmlOutput)
197199
printf("</table></font></center>");
198-
200+
if(webMode)
201+
printf(">>STAMP_MULTI_ALIGN_CONSENSUS_END\n");
199202
}
200203
}
201204

@@ -260,11 +263,14 @@ MultiAlignRec* ProgressiveProfileAlignment::BuildAlignment(PlatformSupport* p, A
260263
//////////////////////////////////////////////////////////////////
261264
PrintMultipleAlignmentConsensus(T->root->alignment);
262265
strcpy(T->root->profile->name, "FBP");
263-
char outFName[STR_LEN];
264-
sprintf(outFName, "%sFBP.txt", outName);
265-
FILE* out=fopen(outFName, "w");
266-
T->root->profile->PrintMotif(out);
267-
fclose(out);
266+
// Only write FBP file when -out was provided (outName is non-empty)
267+
if(strlen(outName) > 0){
268+
char outFName[STR_LEN];
269+
sprintf(outFName, "%sFBP.txt", outName);
270+
FILE* out=fopen(outFName, "w");
271+
T->root->profile->PrintMotif(out);
272+
fclose(out);
273+
}
268274

269275
return(T->root->alignment);
270276
}
@@ -288,6 +294,7 @@ void ProgressiveProfileAlignment::PostorderAlignment(TreeNode* n, TreeNode* star
288294
strcpy(n->alignment->alignedNames[0], n->profile->name);
289295
strcpy(n->alignment->profileAlignment[0]->name, n->profile->name);
290296
n->alignment->alignedIDs[0] = n->leafID;
297+
n->alignment->alignedRC[0] = false; // Leaf is always in forward orientation
291298
//Fill alignSection
292299
for(z=0; z<n->profile->GetLen(); z++)
293300
for(b=0; b<B; b++)
@@ -320,10 +327,14 @@ void ProgressiveProfileAlignment::PostorderAlignment(TreeNode* n, TreeNode* star
320327
strcpy(n->alignment->alignedNames[b], n->left->alignment->alignedNames[b]);
321328
strcpy(n->alignment->profileAlignment[b]->name,n->left->alignment->alignedNames[b]);
322329
n->alignment->alignedIDs[b] = n->left->alignment->alignedIDs[b];
330+
// RC propagation: XOR child's RC state with whether this subtree was reversed
331+
n->alignment->alignedRC[b] = n->left->alignment->alignedRC[b] ^ (!forward1);
323332
}for(b=0; b<n->right->alignment->GetNumAligned(); b++){
324333
strcpy(n->alignment->alignedNames[b+n->left->alignment->GetNumAligned()], n->right->alignment->alignedNames[b]);
325334
strcpy(n->alignment->profileAlignment[b+n->left->alignment->GetNumAligned()]->name, n->right->alignment->alignedNames[b]);
326335
n->alignment->alignedIDs[b+n->left->alignment->GetNumAligned()] = n->right->alignment->alignedIDs[b];
336+
// RC propagation: XOR child's RC state with whether this subtree was reversed
337+
n->alignment->alignedRC[b+n->left->alignment->GetNumAligned()] = n->right->alignment->alignedRC[b] ^ (!forward2);
327338
}
328339
last0=-50; last1=-50;
329340
antiZ=0;
@@ -494,11 +505,14 @@ MultiAlignRec* IterativeRefinementAlignment::BuildAlignment(PlatformSupport* p,
494505
PrintMultipleAlignmentConsensus(alignment);
495506

496507
strcpy(currProfile->name, "FBP");
497-
char outFName[STR_LEN];
498-
sprintf(outFName, "%sFBP.txt", outName);
499-
FILE* out=fopen(outFName, "w");
500-
currProfile->PrintMotif(out);
501-
fclose(out);
508+
// Only write FBP file when -out was provided (outName is non-empty)
509+
if(strlen(outName) > 0){
510+
char outFName[STR_LEN];
511+
sprintf(outFName, "%sFBP.txt", outName);
512+
FILE* out=fopen(outFName, "w");
513+
currProfile->PrintMotif(out);
514+
fclose(out);
515+
}
502516

503517
if(currProfile!=NULL)
504518
delete currProfile;
@@ -538,10 +552,14 @@ MultiAlignRec* MultipleAlignment::SingleProfileAddition(MultiAlignRec* alignment
538552
strcpy(newAlignment->alignedNames[b], alignment->alignedNames[b]);
539553
strcpy(newAlignment->profileAlignment[b]->name, alignment->alignedNames[b]);
540554
newAlignment->alignedIDs[b] = alignment->alignedIDs[b];
555+
// RC propagation: XOR existing RC state with whether this alignment was reversed
556+
newAlignment->alignedRC[b] = alignment->alignedRC[b] ^ (!forward1);
541557
}
542558
strcpy(newAlignment->alignedNames[alignment->GetNumAligned()], two->name);
543559
strcpy(newAlignment->profileAlignment[alignment->GetNumAligned()]->name, two->name);
544560
newAlignment->alignedIDs[alignment->GetNumAligned()] = twoID;
561+
// The new motif's RC state: reverse-complemented if forward2 is false
562+
newAlignment->alignedRC[alignment->GetNumAligned()] = !forward2;
545563

546564
last0=-50; last1=-50;
547565
antiZ=0;
@@ -652,6 +670,7 @@ MultiAlignRec* MultipleAlignment::SingleProfileSubtraction(MultiAlignRec* alignm
652670
strcpy(newAlignment->alignedNames[a], alignment->alignedNames[i]);
653671
strcpy(newAlignment->profileAlignment[a]->name, alignment->alignedNames[i]);
654672
newAlignment->alignedIDs[a] = alignment->alignedIDs[i];
673+
newAlignment->alignedRC[a] = alignment->alignedRC[i]; // Copy RC state
655674
a++;
656675
}
657676
}
@@ -720,3 +739,126 @@ void MultipleAlignment::WeightedFBP(MultiAlignRec* alignment, Motif* currProfile
720739
Plat->n_to_pwm(currProfile);
721740
}
722741

742+
//Print enhanced alignment with strand, offset, and full PFM data to stdout (webmode)
743+
void MultipleAlignment::PrintEnhancedAlignment(MultiAlignRec* alignment)
744+
{
745+
if(alignment == NULL){
746+
alignment = completeAlignment;
747+
if(alignment == NULL){
748+
fprintf(stderr, "Error: complete alignment not yet constructed!\n");
749+
return;
750+
}
751+
}
752+
753+
int z, b;
754+
int aL = alignment->GetAlignL();
755+
756+
if(aL > 0){
757+
printf(">>STAMP_ENHANCED_ALIGNMENT_START\n");
758+
for(int q = 0; q < alignment->GetNumAligned(); q++){
759+
// Print motif header: name, strand (+/-), original ID
760+
printf(">>MOTIF\t%s\t%c\t%d\n",
761+
alignment->alignedNames[q],
762+
alignment->alignedRC[q] ? '-' : '+',
763+
alignment->alignedIDs[q]);
764+
765+
// Print consensus string
766+
printf(">>CONSENSUS\t");
767+
for(z = 0; z < aL; z++){
768+
if(alignment->profileAlignment[q]->f[z][0] == -1)
769+
printf("-");
770+
else
771+
printf("%c", alignment->profileAlignment[q]->ColConsensus(z));
772+
}
773+
printf("\n");
774+
775+
// Print full PFM
776+
printf(">>PFM_START\t%d\n", aL);
777+
for(z = 0; z < aL; z++){
778+
if(alignment->profileAlignment[q]->f[z][0] == -1){
779+
printf("GAP\n");
780+
}else{
781+
for(b = 0; b < B; b++){
782+
printf("%lf", alignment->profileAlignment[q]->f[z][b]);
783+
if(b < B-1) printf("\t");
784+
}
785+
printf("\n");
786+
}
787+
}
788+
printf(">>PFM_END\n");
789+
}
790+
printf(">>STAMP_ENHANCED_ALIGNMENT_END\n");
791+
}
792+
}
793+
794+
//Print FBP profile to stdout in delimited section (webmode)
795+
void MultipleAlignment::PrintFBPToStdout(Motif* fbp)
796+
{
797+
if(fbp == NULL) return;
798+
799+
int i, j;
800+
printf(">>STAMP_FBP_START\n");
801+
printf("DE\t%s\n", fbp->name);
802+
for(i = 0; i < fbp->len; i++){
803+
printf("%d\t", i);
804+
for(j = 0; j < B; j++)
805+
printf("%.4lf\t", fbp->f[i][j]);
806+
printf("%c\n", fbp->ColConsensus(i));
807+
}
808+
printf("XX\n");
809+
printf(">>STAMP_FBP_END\n");
810+
}
811+
812+
//Write enhanced alignment to file (default mode)
813+
void MultipleAlignment::WriteEnhancedAlignment(char* outPrefix, MultiAlignRec* alignment)
814+
{
815+
if(alignment == NULL){
816+
alignment = completeAlignment;
817+
if(alignment == NULL) return;
818+
}
819+
820+
int z, b;
821+
int aL = alignment->GetAlignL();
822+
if(aL <= 0) return;
823+
824+
char outFName[STR_LEN];
825+
sprintf(outFName, "%s_enhanced_alignment.txt", outPrefix);
826+
FILE* out = fopen(outFName, "w");
827+
if(out == NULL){
828+
fprintf(stderr, "Error: can't open output file %s\n", outFName);
829+
return;
830+
}
831+
832+
for(int q = 0; q < alignment->GetNumAligned(); q++){
833+
fprintf(out, ">>MOTIF\t%s\t%c\t%d\n",
834+
alignment->alignedNames[q],
835+
alignment->alignedRC[q] ? '-' : '+',
836+
alignment->alignedIDs[q]);
837+
838+
fprintf(out, ">>CONSENSUS\t");
839+
for(z = 0; z < aL; z++){
840+
if(alignment->profileAlignment[q]->f[z][0] == -1)
841+
fprintf(out, "-");
842+
else
843+
fprintf(out, "%c", alignment->profileAlignment[q]->ColConsensus(z));
844+
}
845+
fprintf(out, "\n");
846+
847+
fprintf(out, ">>PFM_START\t%d\n", aL);
848+
for(z = 0; z < aL; z++){
849+
if(alignment->profileAlignment[q]->f[z][0] == -1){
850+
fprintf(out, "GAP\n");
851+
}else{
852+
for(b = 0; b < B; b++){
853+
fprintf(out, "%lf", alignment->profileAlignment[q]->f[z][b]);
854+
if(b < B-1) fprintf(out, "\t");
855+
}
856+
fprintf(out, "\n");
857+
}
858+
}
859+
fprintf(out, ">>PFM_END\n");
860+
}
861+
862+
fclose(out);
863+
}
864+

0 commit comments

Comments
 (0)