Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/core/net/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class StartConversionJobRequest implements HiCTAPIRequest {
readonly assemblyFilename?: string;
readonly useCurrentAssembly?: boolean;
readonly direction?: string;
readonly outputFilename?: string;
readonly overwrite?: boolean;
readonly resolutions?: string;
readonly compression?: number;
Expand All @@ -205,6 +206,8 @@ class StartConversionJobRequest implements HiCTAPIRequest {
readonly exportMode?: string;
readonly exportAllResolutions?: boolean;
readonly buildResolutionPyramid?: boolean;
readonly balanceInputCoolers?: boolean;
readonly balanceExportedCoolers?: boolean;
readonly binTableFilename?: string;
readonly chromSizesFilename?: string;
readonly binSize?: number;
Expand Down Expand Up @@ -232,6 +235,8 @@ class StartBatchConversionJobsRequest implements HiCTAPIRequest {
readonly exportMode?: string;
readonly exportAllResolutions?: boolean;
readonly buildResolutionPyramid?: boolean;
readonly balanceInputCoolers?: boolean;
readonly balanceExportedCoolers?: boolean;
readonly binTableFilename?: string;
readonly chromSizesFilename?: string;
readonly binSize?: number;
Expand Down
5 changes: 5 additions & 0 deletions src/app/core/net/dto/requestDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ class StartConversionJobRequestDTO extends HiCTAPIRequestDTO<StartConversionJobR
assemblyFilename: this.entity.options.assemblyFilename,
useCurrentAssembly: this.entity.options.useCurrentAssembly,
direction: this.entity.options.direction,
outputFilename: this.entity.options.outputFilename,
overwrite: this.entity.options.overwrite,
resolutions: this.entity.options.resolutions,
compression: this.entity.options.compression,
Expand All @@ -514,6 +515,8 @@ class StartConversionJobRequestDTO extends HiCTAPIRequestDTO<StartConversionJobR
exportMode: this.entity.options.exportMode,
exportAllResolutions: this.entity.options.exportAllResolutions,
buildResolutionPyramid: this.entity.options.buildResolutionPyramid,
balanceInputCoolers: this.entity.options.balanceInputCoolers,
balanceExportedCoolers: this.entity.options.balanceExportedCoolers,
binTableFilename: this.entity.options.binTableFilename,
chromSizesFilename: this.entity.options.chromSizesFilename,
binSize: this.entity.options.binSize,
Expand All @@ -539,6 +542,8 @@ class StartBatchConversionJobsRequestDTO extends HiCTAPIRequestDTO<StartBatchCon
exportMode: this.entity.options.exportMode,
exportAllResolutions: this.entity.options.exportAllResolutions,
buildResolutionPyramid: this.entity.options.buildResolutionPyramid,
balanceInputCoolers: this.entity.options.balanceInputCoolers,
balanceExportedCoolers: this.entity.options.balanceExportedCoolers,
binTableFilename: this.entity.options.binTableFilename,
chromSizesFilename: this.entity.options.chromSizesFilename,
binSize: this.entity.options.binSize,
Expand Down
30 changes: 28 additions & 2 deletions src/app/ui/components/upper_ribbon/FileWizardModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -337,13 +337,22 @@
Drop all precomputed caches before running the wizard
</label>
</div>
<div class="form-check form-switch mt-3">
<div v-if="hasSelectedCoolerSource" class="form-check form-switch mt-3">
<input id="build-resolution-pyramid" v-model="buildResolutionPyramid" class="form-check-input" type="checkbox" />
<label class="form-check-label" for="build-resolution-pyramid">
Build optimized resolution pyramid with hictk
</label>
<div class="form-text">
Enabled by default. HiCT will zoomify and balance converted Cooler inputs before import so sparse or single-resolution files open with a complete set of map resolutions.
Enabled by default. HiCT will zoomify converted Cooler inputs before import so sparse or single-resolution files open with a complete set of map resolutions.
</div>
</div>
<div v-if="hasSelectedCoolerSource" class="form-check form-switch mt-3">
<input id="balance-input-coolers" v-model="balanceInputCoolers" class="form-check-input" type="checkbox" />
<label class="form-check-label" for="balance-input-coolers">
Balance input Coolers with hictk
</label>
<div class="form-text">
Enabled by default for .cool/.mcool inputs. HiCT will run hictk ICE balancing before importing so Cooler weights are available immediately.
</div>
</div>
<div v-if="toolchainStatus && !toolchainStatus.hicConversionAvailable" class="alert alert-warning mt-3 mb-0">
Expand Down Expand Up @@ -913,6 +922,7 @@ const precomputeTracks = ref(true);
const forceTrackPrecompute = ref(false);
const dropCachesBeforeRun = ref(false);
const buildResolutionPyramid = ref(true);
const balanceInputCoolers = ref(true);
const blendMode = ref<WizardBlendMode>("OVER");
const topOpacity = ref(0.5);
const bottomOpacity = ref(1.0);
Expand Down Expand Up @@ -1353,6 +1363,16 @@ const isOpenableAssemblyFilename = (name: string): boolean => {
);
};

const isCoolerFilename = (name: string): boolean => {
const lowered = name.toLowerCase();
return lowered.endsWith(".cool") || lowered.endsWith(".mcool");
};

const hasSelectedCoolerSource = computed(() =>
isCoolerFilename(primarySource.filename) ||
(requiresSecondarySource.value && isCoolerFilename(secondarySource.filename))
);

const stripCompressionSuffix = (name: string): string =>
name.replace(/\.(gz|bgz|xz|zst|zstd|bz2|lz4|lzo)$/i, "");

Expand Down Expand Up @@ -1749,8 +1769,13 @@ const ensureOpenedFilename = async (source: SourceDraft): Promise<string> => {
if (!resolution) {
throw new Error(`Failed to resolve source ${source.filename}`);
}
const forceCoolerPreparation =
isCoolerFilename(source.filename) &&
canConvertSource(source) &&
(buildResolutionPyramid.value || balanceInputCoolers.value);
if (
(!(source.forceConversion && canConvertSource(source))) &&
!forceCoolerPreparation &&
(resolution.action === "OPEN_DIRECT" || resolution.action === "REUSE_CONVERTED")
) {
return resolution.resolvedFilename;
Expand All @@ -1770,6 +1795,7 @@ const ensureOpenedFilename = async (source: SourceDraft): Promise<string> => {
binSize: source.binSize ?? undefined,
countAsFloat: source.countAsFloat || undefined,
buildResolutionPyramid: buildResolutionPyramid.value,
balanceInputCoolers: balanceInputCoolers.value,
})
);
const finishedJob = await waitForConversionJob(started.jobId);
Expand Down
65 changes: 62 additions & 3 deletions src/app/ui/components/upper_ribbon/MatrixExportModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export matrix to .mcool</h5>
<h5 class="modal-title">Export matrix to Cooler</h5>
<button type="button" class="btn-close" @click="emit('dismissed')"></button>
</div>
<div class="modal-body">
Expand Down Expand Up @@ -85,6 +85,20 @@
</div>
</div>

<div class="mb-3">
<label class="form-label">Output Cooler filename</label>
<input
v-model="outputFilename"
class="form-control"
type="text"
placeholder="matrix.mcool"
@input="outputFilenameTouched = true"
/>
<small class="text-muted">
Relative to the HiCT data directory. Use .cool for one selected resolution, or .mcool for multiple resolutions.
</small>
</div>

<div class="mb-3">
<div class="form-check form-switch">
<input
Expand Down Expand Up @@ -169,6 +183,22 @@
Disabled by default: only the finest resolution is exported, so you can zoomify later if needed.
</small>

<div class="form-check form-switch mb-2">
<input
id="export-balance-coolers"
v-model="balanceExportedCoolers"
class="form-check-input"
type="checkbox"
role="switch"
/>
<label class="form-check-label" for="export-balance-coolers">
Balance exported Cooler with hictk
</label>
</div>
<small class="text-muted d-block mb-3">
Enabled by default. Auto and hictk-assisted modes run ICE balancing so downstream Cooler tools can use generated weights.
</small>

<div class="form-check mb-3">
<input
id="export-overwrite"
Expand All @@ -177,7 +207,7 @@
type="checkbox"
/>
<label class="form-check-label" for="export-overwrite">
Overwrite existing .mcool next to source
Overwrite existing output
</label>
</div>

Expand All @@ -198,7 +228,7 @@
@click="startExport"
>
<span v-if="submitting" class="spinner-border spinner-border-sm me-2"></span>
Export .mcool
Export Cooler
</button>
</div>
</div>
Expand Down Expand Up @@ -242,6 +272,9 @@ const compression = ref(6);
const parallelism = ref(Math.max(1, navigator.hardwareConcurrency || 1));
const exportMode = ref("auto");
const exportAllResolutions = ref(false);
const balanceExportedCoolers = ref(true);
const outputFilename = ref("");
const outputFilenameTouched = ref(false);
const selectorKind = ref<"source" | "agp" | null>(null);
const submitting = ref(false);
const errorMessage = ref("");
Expand All @@ -258,6 +291,16 @@ watch(
}
);

watch(
[sourceFilename, exportAllResolutions],
() => {
if (!outputFilenameTouched.value) {
outputFilename.value = defaultOutputFilename(sourceFilename.value, exportAllResolutions.value);
}
},
{ immediate: true }
);

function isHictFilename(filename: string): boolean {
return filename.toLowerCase().endsWith(".hict.hdf5");
}
Expand All @@ -269,12 +312,26 @@ function isAgpFilename(filename: string): boolean {
function onFileSelected(filename: string): void {
if (selectorKind.value === "source") {
sourceFilename.value = filename;
if (!outputFilenameTouched.value) {
outputFilename.value = defaultOutputFilename(filename, exportAllResolutions.value);
}
} else if (selectorKind.value === "agp") {
customAgpFilename.value = filename;
}
selectorKind.value = null;
}

function defaultOutputFilename(filename: string, allResolutions: boolean): string {
if (!filename) {
return "";
}
const extension = allResolutions ? ".mcool" : ".cool";
const stripped = filename
.replace(/\.hict\.hdf5$/i, "")
.replace(/\.hict$/i, "");
return `${stripped}${extension}`;
}

function loadConversionJobs(): void {
jobsLoading.value = true;
props.networkManager.requestManager
Expand Down Expand Up @@ -309,6 +366,7 @@ async function startExport(): Promise<void> {
new StartConversionJobRequest({
filename: sourceFilename.value,
direction: "hict-to-mcool",
outputFilename: outputFilename.value || defaultOutputFilename(sourceFilename.value, exportAllResolutions.value),
useCurrentAssembly: useCurrentAssembly.value,
assemblyFilename: useCurrentAssembly.value ? undefined : customAgpFilename.value || undefined,
overwrite: overwrite.value,
Expand All @@ -317,6 +375,7 @@ async function startExport(): Promise<void> {
parallelism: parallelism.value,
exportMode: exportMode.value,
exportAllResolutions: exportAllResolutions.value,
balanceExportedCoolers: balanceExportedCoolers.value,
})
);
jobId.value = response.jobId;
Expand Down
Loading