python-microscopy 25.6.5__cp39-cp39-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PYME/Acquire/ActionManager.py +450 -0
- PYME/Acquire/ExecTools.py +235 -0
- PYME/Acquire/Hardware/AAOptoelectronics/MDS.py +175 -0
- PYME/Acquire/Hardware/AAOptoelectronics/__init__.py +0 -0
- PYME/Acquire/Hardware/ARCoptix/__init__.py +0 -0
- PYME/Acquire/Hardware/ARCoptix/lcdriver.py +149 -0
- PYME/Acquire/Hardware/ARCoptix/lcserver32.py +123 -0
- PYME/Acquire/Hardware/AndorIXon/AndorCam.py +1095 -0
- PYME/Acquire/Hardware/AndorIXon/AndorControlFrame.py +312 -0
- PYME/Acquire/Hardware/AndorIXon/AndorIXon.py +737 -0
- PYME/Acquire/Hardware/AndorIXon/__init__.py +27 -0
- PYME/Acquire/Hardware/AndorNeo/AndorNeo.py +644 -0
- PYME/Acquire/Hardware/AndorNeo/AndorNeoControlFrame.py +161 -0
- PYME/Acquire/Hardware/AndorNeo/AndorZyla.py +761 -0
- PYME/Acquire/Hardware/AndorNeo/SDK3.py +307 -0
- PYME/Acquire/Hardware/AndorNeo/SDK3Cam.py +171 -0
- PYME/Acquire/Hardware/AndorNeo/ZylaControlPanel.py +105 -0
- PYME/Acquire/Hardware/AndorNeo/__init__.py +22 -0
- PYME/Acquire/Hardware/AndorNeo/parseAtdebug.py +46 -0
- PYME/Acquire/Hardware/AndorNeo/plotTimings.py +73 -0
- PYME/Acquire/Hardware/AndorNeo/testNeo.py +62 -0
- PYME/Acquire/Hardware/Camera.py +1199 -0
- PYME/Acquire/Hardware/CameraSkeleton.py +642 -0
- PYME/Acquire/Hardware/Coherent/OBIS.py +147 -0
- PYME/Acquire/Hardware/Coherent/Sapphire.py +162 -0
- PYME/Acquire/Hardware/Coherent/__init__.py +0 -0
- PYME/Acquire/Hardware/DMDGui.py +198 -0
- PYME/Acquire/Hardware/DigiData/DigiData.py +95 -0
- PYME/Acquire/Hardware/DigiData/DigiDataClient.py +30 -0
- PYME/Acquire/Hardware/DigiData/RemoteDigiData.py +51 -0
- PYME/Acquire/Hardware/DigiData/__init__.py +24 -0
- PYME/Acquire/Hardware/DigiData/axDD132x.py +348 -0
- PYME/Acquire/Hardware/EMCCDTheory.py +41 -0
- PYME/Acquire/Hardware/ExciterWheel.py +167 -0
- PYME/Acquire/Hardware/FilterWheel.py +184 -0
- PYME/Acquire/Hardware/FocCorr.py +106 -0
- PYME/Acquire/Hardware/FocCorrR.py +196 -0
- PYME/Acquire/Hardware/FrFilter.py +51 -0
- PYME/Acquire/Hardware/GCS/GCS_DLL.py +370 -0
- PYME/Acquire/Hardware/GCS/__init__.py +0 -0
- PYME/Acquire/Hardware/GCS/gcs.py +210 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/HamamatsuDCAM.py +662 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/HamamatsuORCA.py +602 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/Hamamatsu_control_panel.py +54 -0
- PYME/Acquire/Hardware/HamamatsuDCAM/__init__.py +23 -0
- PYME/Acquire/Hardware/LaserControlFrame.py +160 -0
- PYME/Acquire/Hardware/MPBCommunications/MPBCW.py +132 -0
- PYME/Acquire/Hardware/MPBCommunications/__init__.py +0 -0
- PYME/Acquire/Hardware/Mercury/Mercury.py +269 -0
- PYME/Acquire/Hardware/Mercury/PI_Mercury_GCS_DLL.py +857 -0
- PYME/Acquire/Hardware/Mercury/__init__.py +22 -0
- PYME/Acquire/Hardware/Mercury/mercuryStepper.py +203 -0
- PYME/Acquire/Hardware/Mercury/mercuryStepperGCS.py +281 -0
- PYME/Acquire/Hardware/NikonTE2000.py +68 -0
- PYME/Acquire/Hardware/NikonTi.py +214 -0
- PYME/Acquire/Hardware/NikonTiGUI.py +81 -0
- PYME/Acquire/Hardware/OrielCornerstone.py +125 -0
- PYME/Acquire/Hardware/PM100USB.py +51 -0
- PYME/Acquire/Hardware/Piezos/__init__.py +22 -0
- PYME/Acquire/Hardware/Piezos/base_piezo.py +108 -0
- PYME/Acquire/Hardware/Piezos/offsetPiezo.py +162 -0
- PYME/Acquire/Hardware/Piezos/offsetPiezoREST.py +308 -0
- PYME/Acquire/Hardware/Piezos/piezo_c867.py +486 -0
- PYME/Acquire/Hardware/Piezos/piezo_e255.py +68 -0
- PYME/Acquire/Hardware/Piezos/piezo_e662.py +61 -0
- PYME/Acquire/Hardware/Piezos/piezo_e709.py +434 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816.py +452 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816_corr.py +70 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816_dll.py +518 -0
- PYME/Acquire/Hardware/Piezos/piezo_e816b.py +70 -0
- PYME/Acquire/Hardware/Piezos/piezo_pipython_gcs.py +560 -0
- PYME/Acquire/Hardware/Piezos/piezo_test.py +55 -0
- PYME/Acquire/Hardware/Simulator/EmpiricalHist.py +273 -0
- PYME/Acquire/Hardware/Simulator/__init__.py +22 -0
- PYME/Acquire/Hardware/Simulator/dSimControl.py +961 -0
- PYME/Acquire/Hardware/Simulator/fakeCam.py +651 -0
- PYME/Acquire/Hardware/Simulator/fakePiezo.py +95 -0
- PYME/Acquire/Hardware/Simulator/fluor.py +336 -0
- PYME/Acquire/Hardware/Simulator/illuminate.cp39-win_amd64.dll.a +0 -0
- PYME/Acquire/Hardware/Simulator/illuminate.cp39-win_amd64.pyd +0 -0
- PYME/Acquire/Hardware/Simulator/illuminate.pyx +93 -0
- PYME/Acquire/Hardware/Simulator/lasersliders.py +90 -0
- PYME/Acquire/Hardware/Simulator/rend_im.py +446 -0
- PYME/Acquire/Hardware/Simulator/setup.py +69 -0
- PYME/Acquire/Hardware/Simulator/simcontrol.py +348 -0
- PYME/Acquire/Hardware/Simulator/simui_wx.py +794 -0
- PYME/Acquire/Hardware/Tango/__init__.py +0 -0
- PYME/Acquire/Hardware/Tango/marzhauser_tango.py +583 -0
- PYME/Acquire/Hardware/TiLightCrafter.py +388 -0
- PYME/Acquire/Hardware/__init__.py +22 -0
- PYME/Acquire/Hardware/aotf.py +271 -0
- PYME/Acquire/Hardware/arclampshutterpanel.py +71 -0
- PYME/Acquire/Hardware/cameraSoftwareBuffer.py +215 -0
- PYME/Acquire/Hardware/camera_noise.py +270 -0
- PYME/Acquire/Hardware/ccdAdjPanel.py +188 -0
- PYME/Acquire/Hardware/ccdCalibrator.py +161 -0
- PYME/Acquire/Hardware/cobaltLaser.py +108 -0
- PYME/Acquire/Hardware/cobaltLaser561.py +94 -0
- PYME/Acquire/Hardware/comports.py +26 -0
- PYME/Acquire/Hardware/driftTrackGUI.py +488 -0
- PYME/Acquire/Hardware/driftTracking.py +595 -0
- PYME/Acquire/Hardware/fakeShutters.py +52 -0
- PYME/Acquire/Hardware/focusKeys.py +204 -0
- PYME/Acquire/Hardware/focus_locks/__init__.py +0 -0
- PYME/Acquire/Hardware/focus_locks/reflection_focus_lock.py +684 -0
- PYME/Acquire/Hardware/frZStage.py +117 -0
- PYME/Acquire/Hardware/fw102.py +38 -0
- PYME/Acquire/Hardware/ids_peak_cam.py +561 -0
- PYME/Acquire/Hardware/ioslave.py +305 -0
- PYME/Acquire/Hardware/lasers.py +292 -0
- PYME/Acquire/Hardware/matchboxLaser.py +58 -0
- PYME/Acquire/Hardware/microscope_adapter.py +104 -0
- PYME/Acquire/Hardware/mpd_picosecond_delayer.py +395 -0
- PYME/Acquire/Hardware/multiview.py +287 -0
- PYME/Acquire/Hardware/olympusix81.py +59 -0
- PYME/Acquire/Hardware/pco/__init__.py +0 -0
- PYME/Acquire/Hardware/pco/pco_cam.py +348 -0
- PYME/Acquire/Hardware/pco/pco_edge_42_lt.py +34 -0
- PYME/Acquire/Hardware/pco/pco_sdk.py +1645 -0
- PYME/Acquire/Hardware/pco/pco_sdk_cam.py +518 -0
- PYME/Acquire/Hardware/pco/pco_sdk_cam_control_panel.py +54 -0
- PYME/Acquire/Hardware/phoxxLaser.py +199 -0
- PYME/Acquire/Hardware/phoxxLaserOLD.py +242 -0
- PYME/Acquire/Hardware/priorLumen.py +102 -0
- PYME/Acquire/Hardware/priorarclampshutter.py +52 -0
- PYME/Acquire/Hardware/setup.py +50 -0
- PYME/Acquire/Hardware/spacenav.py +148 -0
- PYME/Acquire/Hardware/splitter.py +394 -0
- PYME/Acquire/Hardware/thorlabsPiezo.py +98 -0
- PYME/Acquire/Hardware/thorlabs_cam.py +136 -0
- PYME/Acquire/Hardware/thorlabs_mff_flipper.py +400 -0
- PYME/Acquire/Hardware/toptica_ibeam.py +51 -0
- PYME/Acquire/Hardware/uc480/AndorControlFrame.py +308 -0
- PYME/Acquire/Hardware/uc480/__init__.py +27 -0
- PYME/Acquire/Hardware/uc480/uCam480.py +695 -0
- PYME/Acquire/Hardware/uc480/uc480.py +189 -0
- PYME/Acquire/Hardware/uc480/uc480Deprecated.py +87 -0
- PYME/Acquire/Hardware/uc480/uc480_h.py +767 -0
- PYME/Acquire/Hardware/uc480/uc480_h_gen.py +62 -0
- PYME/Acquire/Hardware/uc480/ucCamControlFrame.py +83 -0
- PYME/Acquire/Hardware/ueye.py +702 -0
- PYME/Acquire/PYMEAcquire.py +186 -0
- PYME/Acquire/Protocols/DMDMFM.py +74 -0
- PYME/Acquire/Protocols/__init__.py +22 -0
- PYME/Acquire/Protocols/darkCalibrate.py +56 -0
- PYME/Acquire/Protocols/dual671_470.py +45 -0
- PYME/Acquire/Protocols/dual671_488.py +46 -0
- PYME/Acquire/Protocols/gainCal.py +53 -0
- PYME/Acquire/Protocols/htsms-cal-psf.py +32 -0
- PYME/Acquire/Protocols/htsms-cal-registration.py +52 -0
- PYME/Acquire/Protocols/htsms-flow.py +29 -0
- PYME/Acquire/Protocols/htsms-staggered.py +31 -0
- PYME/Acquire/Protocols/htsms-tile.py +68 -0
- PYME/Acquire/Protocols/htsms-two-color.py +29 -0
- PYME/Acquire/Protocols/htsms-widefield.py +32 -0
- PYME/Acquire/Protocols/paint671.py +61 -0
- PYME/Acquire/Protocols/photoconversion.py +52 -0
- PYME/Acquire/Protocols/photoconversion2.py +55 -0
- PYME/Acquire/Protocols/prebleach488.py +70 -0
- PYME/Acquire/Protocols/prebleach490.py +69 -0
- PYME/Acquire/Protocols/prebleach561.py +72 -0
- PYME/Acquire/Protocols/prebleach561NeomEos2.py +70 -0
- PYME/Acquire/Protocols/prebleach561NeomEos2ND1.py +70 -0
- PYME/Acquire/Protocols/prebleach642.py +70 -0
- PYME/Acquire/Protocols/prebleach671.py +69 -0
- PYME/Acquire/Protocols/prebleach671ND1.py +58 -0
- PYME/Acquire/Protocols/prebleach671ND2.py +57 -0
- PYME/Acquire/Protocols/prebleach671Neo.py +69 -0
- PYME/Acquire/Protocols/recover671.py +62 -0
- PYME/Acquire/Protocols/sequence642_488.py +85 -0
- PYME/Acquire/Protocols/sequence642_561.py +87 -0
- PYME/Acquire/Protocols/shiftfield.py +198 -0
- PYME/Acquire/Protocols/simul488.py +59 -0
- PYME/Acquire/Protocols/simul642.py +63 -0
- PYME/Acquire/Protocols/simul642HTSMS.py +63 -0
- PYME/Acquire/Protocols/simulPA.py +37 -0
- PYME/Acquire/Protocols/simulSIM.py +76 -0
- PYME/Acquire/Protocols/simulStep.py +62 -0
- PYME/Acquire/Protocols/spdscan.py +178 -0
- PYME/Acquire/Protocols/standard470.py +45 -0
- PYME/Acquire/Protocols/standard488.py +47 -0
- PYME/Acquire/Protocols/standard671.py +48 -0
- PYME/Acquire/Protocols/standard671_no532.py +47 -0
- PYME/Acquire/Protocols/standardArclamp.py +46 -0
- PYME/Acquire/Protocols/strip_tile.py +204 -0
- PYME/Acquire/Protocols/tile.py +204 -0
- PYME/Acquire/Protocols/tile3D.py +192 -0
- PYME/Acquire/Protocols/tile671.py +190 -0
- PYME/Acquire/Protocols/tile_triggered.py +189 -0
- PYME/Acquire/Scripts/init.py +232 -0
- PYME/Acquire/Scripts/init_N1_Ti_Exeter_ZIx.py +374 -0
- PYME/Acquire/Scripts/init_N2_Ti_Zyla_lasers.py +290 -0
- PYME/Acquire/Scripts/init_Neo.py +266 -0
- PYME/Acquire/Scripts/init_NeoSim.py +159 -0
- PYME/Acquire/Scripts/init_TIRF.py +232 -0
- PYME/Acquire/Scripts/init_TIRF_Neo.py +242 -0
- PYME/Acquire/Scripts/init_TIRF_NeoO.py +241 -0
- PYME/Acquire/Scripts/init_TIRF_onecam.py +233 -0
- PYME/Acquire/Scripts/init_Ti.py +257 -0
- PYME/Acquire/Scripts/init_UOA_n.py +256 -0
- PYME/Acquire/Scripts/init_UOA_n2.py +246 -0
- PYME/Acquire/Scripts/init_Y1.py +249 -0
- PYME/Acquire/Scripts/init_Zyla.py +260 -0
- PYME/Acquire/Scripts/init_drift_tracking.py +80 -0
- PYME/Acquire/Scripts/init_emccd_basic.py +277 -0
- PYME/Acquire/Scripts/init_htsms.py +277 -0
- PYME/Acquire/Scripts/init_htsms_focus_lock.py +132 -0
- PYME/Acquire/Scripts/init_micrpi.py +45 -0
- PYME/Acquire/Scripts/init_orca.py +70 -0
- PYME/Acquire/Scripts/init_pco.py +49 -0
- PYME/Acquire/Scripts/init_rev.py +74 -0
- PYME/Acquire/Scripts/init_sim100.py +169 -0
- PYME/Acquire/Scripts/init_sim103.py +169 -0
- PYME/Acquire/Scripts/init_sim2.py +211 -0
- PYME/Acquire/Scripts/init_sim50.py +169 -0
- PYME/Acquire/Scripts/init_sim75.py +169 -0
- PYME/Acquire/Scripts/init_sim_drift_tracking.py +166 -0
- PYME/Acquire/Scripts/init_sim_htsms.py +224 -0
- PYME/Acquire/Scripts/init_sim_htsms_n.py +233 -0
- PYME/Acquire/Scripts/init_sim_main.py +229 -0
- PYME/Acquire/Scripts/init_sim_min.py +197 -0
- PYME/Acquire/Scripts/init_sim_rem.py +176 -0
- PYME/Acquire/Scripts/init_smi1.py +110 -0
- PYME/Acquire/Scripts/init_spectro.py +78 -0
- PYME/Acquire/Scripts/init_spim.py +116 -0
- PYME/Acquire/Scripts/init_thorcam.py +62 -0
- PYME/Acquire/Scripts/init_twophoton.py +112 -0
- PYME/Acquire/Scripts/init_uc480.py +262 -0
- PYME/Acquire/Scripts/init_ueye.py +261 -0
- PYME/Acquire/SpoolController.py +958 -0
- PYME/Acquire/Utils/MultiPointScanner.py +80 -0
- PYME/Acquire/Utils/__init__.py +13 -0
- PYME/Acquire/Utils/failsafe.py +157 -0
- PYME/Acquire/Utils/fastTiler.py +153 -0
- PYME/Acquire/Utils/pointScanner.py +496 -0
- PYME/Acquire/Utils/sarcSpacing.py +86 -0
- PYME/Acquire/Utils/strip_tiler.py +93 -0
- PYME/Acquire/Utils/tiler.py +464 -0
- PYME/Acquire/Utils/vibrationAnalysis.py +65 -0
- PYME/Acquire/__init__.py +44 -0
- PYME/Acquire/acquire_client.py +90 -0
- PYME/Acquire/acquire_server.py +316 -0
- PYME/Acquire/acquirebase.py +139 -0
- PYME/Acquire/acquiremainframe.py +716 -0
- PYME/Acquire/acquirewx.py +61 -0
- PYME/Acquire/acquisition_base.py +148 -0
- PYME/Acquire/actions.py +209 -0
- PYME/Acquire/autofocus.py +54 -0
- PYME/Acquire/eventLog.py +95 -0
- PYME/Acquire/event_loop.py +119 -0
- PYME/Acquire/frameWrangler.py +530 -0
- PYME/Acquire/htsms/__init__.py +8 -0
- PYME/Acquire/htsms/rule_ui.py +970 -0
- PYME/Acquire/htsms/rule_ui_v2.py +573 -0
- PYME/Acquire/htsms/tweeter.py +222 -0
- PYME/Acquire/microscope.py +1050 -0
- PYME/Acquire/positionTracker.py +181 -0
- PYME/Acquire/protocol.py +299 -0
- PYME/Acquire/protocol_acquisition.py +404 -0
- PYME/Acquire/sampleInformation.py +591 -0
- PYME/Acquire/sampleInformationDjangoDirect.py +446 -0
- PYME/Acquire/setup.py +52 -0
- PYME/Acquire/stackSettings.py +295 -0
- PYME/Acquire/stage_leveling.py +285 -0
- PYME/Acquire/ui/AnalysisSettingsUI.py +184 -0
- PYME/Acquire/ui/HDFSpoolFrame.py +584 -0
- PYME/Acquire/ui/__init__.py +0 -0
- PYME/Acquire/ui/actionUI.py +892 -0
- PYME/Acquire/ui/focus_lock_gui.py +149 -0
- PYME/Acquire/ui/intensity_trace.py +143 -0
- PYME/Acquire/ui/intsliders.py +222 -0
- PYME/Acquire/ui/lasersliders.py +329 -0
- PYME/Acquire/ui/mpd_picosecond_delay_panel.py +69 -0
- PYME/Acquire/ui/multiview_select.py +77 -0
- PYME/Acquire/ui/positionUI.py +519 -0
- PYME/Acquire/ui/preflight.py +58 -0
- PYME/Acquire/ui/scanner_panel.py +41 -0
- PYME/Acquire/ui/selectCameraPanel.py +46 -0
- PYME/Acquire/ui/seqdialog.py +501 -0
- PYME/Acquire/ui/splashScreen.py +175 -0
- PYME/Acquire/ui/spool_panel.py +830 -0
- PYME/Acquire/ui/tile_panel.py +556 -0
- PYME/Acquire/ui/tilesettingsui.py +91 -0
- PYME/Acquire/ui/voxelSizeDialog.py +149 -0
- PYME/Acquire/webui/__init__.py +36 -0
- PYME/Acquire/webui/ipy.py +361 -0
- PYME/Acquire/webui/static/css/pyme-bootstrap.css +9908 -0
- PYME/Acquire/webui/static/css/pymeacquire.css +85 -0
- PYME/Acquire/webui/static/js/pymeacquire.js +375 -0
- PYME/Acquire/webui/static/js/pzf.js +95 -0
- PYME/Acquire/webui/templates/PYMEAcquire.html +232 -0
- PYME/Acquire/webui/templates/login.html +84 -0
- PYME/Acquire/webui/templates/xtermpage.html +106 -0
- PYME/Acquire/xyztc.py +418 -0
- PYME/Acquire/zScanner.py +287 -0
- PYME/Analysis/BleachProfile/__init__.py +26 -0
- PYME/Analysis/BleachProfile/deMod.py +91 -0
- PYME/Analysis/BleachProfile/fitRecover.py +90 -0
- PYME/Analysis/BleachProfile/intensProf.py +519 -0
- PYME/Analysis/BleachProfile/kinModels.py +507 -0
- PYME/Analysis/BleachProfile/rawIntensity.py +227 -0
- PYME/Analysis/BleachProfile/rollins.py +555 -0
- PYME/Analysis/Colocalisation/__init__.py +27 -0
- PYME/Analysis/Colocalisation/colocScaleDep.py +126 -0
- PYME/Analysis/Colocalisation/correlationCoeffs.py +198 -0
- PYME/Analysis/Colocalisation/distColoc.py +110 -0
- PYME/Analysis/Colocalisation/edtColoc.py +191 -0
- PYME/Analysis/MetaData.py +278 -0
- PYME/Analysis/PSFEst/__init__.py +27 -0
- PYME/Analysis/PSFEst/dec.py +283 -0
- PYME/Analysis/PSFEst/extractImages.py +316 -0
- PYME/Analysis/PSFEst/fit_psf_zernikes.py +194 -0
- PYME/Analysis/PSFEst/psfQuality.py +225 -0
- PYME/Analysis/PSFGen/__init__.py +40 -0
- PYME/Analysis/PSFGen/fourier.py +138 -0
- PYME/Analysis/PSFGen/fourierHNA.py +1108 -0
- PYME/Analysis/PSFGen/fourierHNA2.py +979 -0
- PYME/Analysis/PSFGen/genCRBcurves.py +185 -0
- PYME/Analysis/PSFGen/nijboer_zernike.py +79 -0
- PYME/Analysis/PSFGen/ps_app.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/PSFGen/ps_app.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/PSFGen/test_ps_app.py +24 -0
- PYME/Analysis/Tracking/__init__.py +3 -0
- PYME/Analysis/Tracking/msd.py +57 -0
- PYME/Analysis/Tracking/trackUtils.py +528 -0
- PYME/Analysis/Tracking/tracking.py +152 -0
- PYME/Analysis/__init__.py +30 -0
- PYME/Analysis/_fithelpers.py +161 -0
- PYME/Analysis/angleFilter.py +287 -0
- PYME/Analysis/annealThresh.py +157 -0
- PYME/Analysis/autoencoder_classifier.py +45 -0
- PYME/Analysis/binAvg.py +144 -0
- PYME/Analysis/composite.py +183 -0
- PYME/Analysis/cramerRao.py +259 -0
- PYME/Analysis/deTile.py +441 -0
- PYME/Analysis/density_filter.py +45 -0
- PYME/Analysis/distributed_pyramid.py +426 -0
- PYME/Analysis/fiducial_matching.py +220 -0
- PYME/Analysis/gen_sCMOS_maps.py +355 -0
- PYME/Analysis/graphing_filters.py +311 -0
- PYME/Analysis/implicitFilter.py +359 -0
- PYME/Analysis/keep_pairs.py +69 -0
- PYME/Analysis/optic_flow.py +108 -0
- PYME/Analysis/piecewise.py +36 -0
- PYME/Analysis/piecewiseMapping.py +304 -0
- PYME/Analysis/piezo_movement_correction.py +153 -0
- PYME/Analysis/points/DeClump/__init__.py +78 -0
- PYME/Analysis/points/DeClump/deClump.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/DeClump/deClump.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/DeClump/deClumpGUI.py +87 -0
- PYME/Analysis/points/DeClump/pyDeClump.py +284 -0
- PYME/Analysis/points/DeClump/testClump.py +40 -0
- PYME/Analysis/points/DistHist/__init__.py +25 -0
- PYME/Analysis/points/DistHist/distHist.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/DistHist/distHist.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/DistHist/distHistThreaded.py +71 -0
- PYME/Analysis/points/DistHist/setup.py +54 -0
- PYME/Analysis/points/EdgeDB/__init__.py +24 -0
- PYME/Analysis/points/EdgeDB/edgeDB.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/EdgeDB/edgeDB.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/EdgeDB/edgeTest.py +66 -0
- PYME/Analysis/points/EdgeDB/edges.py +102 -0
- PYME/Analysis/points/EdgeDB/segment.py +75 -0
- PYME/Analysis/points/EdgeDB/setup.py +54 -0
- PYME/Analysis/points/QuadTree/QTrend.py +232 -0
- PYME/Analysis/points/QuadTree/__init__.py +25 -0
- PYME/Analysis/points/QuadTree/pointQT.py +227 -0
- PYME/Analysis/points/QuadTree/pointQTF.py +254 -0
- PYME/Analysis/points/QuadTree/qtUtils.py +36 -0
- PYME/Analysis/points/QuadTree/rendQT.py +118 -0
- PYME/Analysis/points/SoftRend/__init__.py +24 -0
- PYME/Analysis/points/SoftRend/setup.py +61 -0
- PYME/Analysis/points/SoftRend/testTetrahedra.py +41 -0
- PYME/Analysis/points/SoftRend/triRend.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/SoftRend/triRend.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/__init__.py +3 -0
- PYME/Analysis/points/arcfit/__init__.py +1 -0
- PYME/Analysis/points/arcfit/arcmf.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/arcfit/arcmf.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/arcfit/setup.py +51 -0
- PYME/Analysis/points/astigmatism/__init__.py +0 -0
- PYME/Analysis/points/astigmatism/astigTools.py +338 -0
- PYME/Analysis/points/astigmatism/astiglookup.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/astigmatism/astiglookup.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/astigmatism/setup.py +54 -0
- PYME/Analysis/points/cluster_morphology.py +199 -0
- PYME/Analysis/points/coordinate_tools.py +278 -0
- PYME/Analysis/points/features/__init__.py +0 -0
- PYME/Analysis/points/features/metal.py +46 -0
- PYME/Analysis/points/features/shaders/vect_feat_3d.metal +49 -0
- PYME/Analysis/points/fiducials.py +142 -0
- PYME/Analysis/points/gen3DTriangs.py +796 -0
- PYME/Analysis/points/moments.py +143 -0
- PYME/Analysis/points/multiview.py +458 -0
- PYME/Analysis/points/objectMeasure.py +206 -0
- PYME/Analysis/points/qpaint.py +97 -0
- PYME/Analysis/points/rings.py +372 -0
- PYME/Analysis/points/ripleys.py +427 -0
- PYME/Analysis/points/segmentLL.py +134 -0
- PYME/Analysis/points/setup.py +56 -0
- PYME/Analysis/points/spherical_harmonics.py +909 -0
- PYME/Analysis/points/surfit.py +829 -0
- PYME/Analysis/points/tCluster.py +75 -0
- PYME/Analysis/points/traveling_salesperson/__init__.py +0 -0
- PYME/Analysis/points/traveling_salesperson/queue_opt.py +117 -0
- PYME/Analysis/points/traveling_salesperson/sectioned_two_opt.py +327 -0
- PYME/Analysis/points/traveling_salesperson/setup.py +40 -0
- PYME/Analysis/points/traveling_salesperson/sort.py +78 -0
- PYME/Analysis/points/traveling_salesperson/two_opt.py +189 -0
- PYME/Analysis/points/traveling_salesperson/two_opt_utils.cp39-win_amd64.dll.a +0 -0
- PYME/Analysis/points/traveling_salesperson/two_opt_utils.cp39-win_amd64.pyd +0 -0
- PYME/Analysis/points/traveling_salesperson/two_opt_utils.pyx +41 -0
- PYME/Analysis/points/twoColour.py +572 -0
- PYME/Analysis/points/twoColourPlot.py +107 -0
- PYME/Analysis/processLogger.py +129 -0
- PYME/Analysis/profile_extraction.py +72 -0
- PYME/Analysis/setup.py +60 -0
- PYME/Analysis/splitting.py +372 -0
- PYME/Analysis/svmSegment.py +214 -0
- PYME/Analysis/thresholding.py +192 -0
- PYME/Analysis/tile_pyramid.py +938 -0
- PYME/Analysis/widefieldify.py +54 -0
- PYME/DSView/DisplayOptionsPanel.py +503 -0
- PYME/DSView/LUT/__init__.py +33 -0
- PYME/DSView/LUT/lut.cp39-win_amd64.dll.a +0 -0
- PYME/DSView/LUT/lut.cp39-win_amd64.pyd +0 -0
- PYME/DSView/LUT/setup.py +54 -0
- PYME/DSView/OpenSequenceDialog.py +83 -0
- PYME/DSView/OverlaysPanel.py +111 -0
- PYME/DSView/__init__.py +32 -0
- PYME/DSView/arrayViewPanel.py +1035 -0
- PYME/DSView/displayOptions.py +453 -0
- PYME/DSView/displaySettingsPanel.py +271 -0
- PYME/DSView/dsviewer.py +542 -0
- PYME/DSView/eventLogViewer.py +805 -0
- PYME/DSView/fitInfo.py +486 -0
- PYME/DSView/htmlServe.py +72 -0
- PYME/DSView/logparser.py +100 -0
- PYME/DSView/modules/LMAnalysis.py +1053 -0
- PYME/DSView/modules/LMDisplay.py +194 -0
- PYME/DSView/modules/__init__.py +162 -0
- PYME/DSView/modules/_base.py +39 -0
- PYME/DSView/modules/annotation.py +725 -0
- PYME/DSView/modules/arrayView.py +71 -0
- PYME/DSView/modules/blobFinding.py +455 -0
- PYME/DSView/modules/blobMeasure.py +1093 -0
- PYME/DSView/modules/camera_maps.py +38 -0
- PYME/DSView/modules/coloc.py +414 -0
- PYME/DSView/modules/composite.py +715 -0
- PYME/DSView/modules/convnet.py +24 -0
- PYME/DSView/modules/cropping.py +133 -0
- PYME/DSView/modules/dark_and_flatfield.py +31 -0
- PYME/DSView/modules/deconvolution.py +499 -0
- PYME/DSView/modules/eventView.py +80 -0
- PYME/DSView/modules/extractBlobs.py +46 -0
- PYME/DSView/modules/fastGraphPanel.py +85 -0
- PYME/DSView/modules/filtering.py +335 -0
- PYME/DSView/modules/flowView.py +268 -0
- PYME/DSView/modules/fourierRingCorrelation.py +172 -0
- PYME/DSView/modules/gl_image_view.py +12 -0
- PYME/DSView/modules/graphViewPanel.py +272 -0
- PYME/DSView/modules/measure/__init__.py +1 -0
- PYME/DSView/modules/measure/filterPan.py +239 -0
- PYME/DSView/modules/metadataView.py +28 -0
- PYME/DSView/modules/multithreshold.py +98 -0
- PYME/DSView/modules/neuroglancer.py +129 -0
- PYME/DSView/modules/object_tagging.py +157 -0
- PYME/DSView/modules/particleTracking.py +463 -0
- PYME/DSView/modules/playback.py +256 -0
- PYME/DSView/modules/prebleachExtraction.py +75 -0
- PYME/DSView/modules/profileFitting.py +137 -0
- PYME/DSView/modules/profilePlotting.py +408 -0
- PYME/DSView/modules/projections.py +48 -0
- PYME/DSView/modules/psfExtraction.py +570 -0
- PYME/DSView/modules/psfGeneration.py +138 -0
- PYME/DSView/modules/psfTools.py +661 -0
- PYME/DSView/modules/pupilTools.py +204 -0
- PYME/DSView/modules/pupilView.py +139 -0
- PYME/DSView/modules/recipes.py +266 -0
- PYME/DSView/modules/redimension.py +75 -0
- PYME/DSView/modules/segmentation.py +192 -0
- PYME/DSView/modules/sheetView.py +24 -0
- PYME/DSView/modules/shell.py +39 -0
- PYME/DSView/modules/shiftfield_estimation.py +433 -0
- PYME/DSView/modules/sparkprepare.py +94 -0
- PYME/DSView/modules/splitter.py +148 -0
- PYME/DSView/modules/synchronise.py +64 -0
- PYME/DSView/modules/templates/astigCal.html +42 -0
- PYME/DSView/modules/templates/measureView.html +69 -0
- PYME/DSView/modules/templates/measureView2.html +48 -0
- PYME/DSView/modules/templates/measureView3.html +98 -0
- PYME/DSView/modules/templates/trackView.html +139 -0
- PYME/DSView/modules/templates/tracksOverview.html +21 -0
- PYME/DSView/modules/tiling.py +147 -0
- PYME/DSView/modules/unmixing.py +148 -0
- PYME/DSView/modules/vis3D.py +320 -0
- PYME/DSView/modules/visgui.py +163 -0
- PYME/DSView/modules/webview.py +14 -0
- PYME/DSView/overlays.py +295 -0
- PYME/DSView/scrolledImagePanel.py +188 -0
- PYME/DSView/setup.py +53 -0
- PYME/DSView/splashScreen.py +158 -0
- PYME/DSView/voxSizeDialog.py +84 -0
- PYME/Deconv/__init__.py +35 -0
- PYME/Deconv/beadGen.py +55 -0
- PYME/Deconv/dec.py +659 -0
- PYME/Deconv/decTask.py +65 -0
- PYME/Deconv/decThread.py +113 -0
- PYME/Deconv/dec_test.py +47 -0
- PYME/Deconv/deconvDialogs.py +417 -0
- PYME/Deconv/fftwWisdom.py +62 -0
- PYME/Deconv/richardsonLucy.py +141 -0
- PYME/Deconv/richardsonLucyMV.py +151 -0
- PYME/Deconv/richardsonLucyMVM.py +218 -0
- PYME/Deconv/test_block_dec.py +46 -0
- PYME/Deconv/tq_block_dec.py +331 -0
- PYME/Deconv/wiener.py +163 -0
- PYME/IO/DataSources/AlignDataSource.py +91 -0
- PYME/IO/DataSources/ArrayDataSource.py +260 -0
- PYME/IO/DataSources/BGSDataSource.py +353 -0
- PYME/IO/DataSources/BaseDataSource.py +457 -0
- PYME/IO/DataSources/BioformatsDataSource.py +178 -0
- PYME/IO/DataSources/BufferedDataSource.py +99 -0
- PYME/IO/DataSources/ClusterPZFDataSource.py +124 -0
- PYME/IO/DataSources/ConcatenatedDataSource.py +56 -0
- PYME/IO/DataSources/CropDataSource.py +243 -0
- PYME/IO/DataSources/DcimgDataSource.py +54 -0
- PYME/IO/DataSources/ElectronsPerSecondDataSource.py +61 -0
- PYME/IO/DataSources/FlatFieldDataSource.py +77 -0
- PYME/IO/DataSources/HDFDataSource.py +160 -0
- PYME/IO/DataSources/HTTPDataSource.py +80 -0
- PYME/IO/DataSources/ImageSeriesDataSource.py +123 -0
- PYME/IO/DataSources/MultiviewDataSource.py +99 -0
- PYME/IO/DataSources/OBFDataSource.py +67 -0
- PYME/IO/DataSources/RandomDataSource.py +23 -0
- PYME/IO/DataSources/SupertileDatasource.py +148 -0
- PYME/IO/DataSources/TQDataSource.py +41 -0
- PYME/IO/DataSources/TiffDataSource.py +193 -0
- PYME/IO/DataSources/TileDataSource.py +91 -0
- PYME/IO/DataSources/UnsplitDataSource.py +92 -0
- PYME/IO/DataSources/__init__.py +59 -0
- PYME/IO/FileUtils/IJLut.py +43 -0
- PYME/IO/FileUtils/RawToHdf5.py +105 -0
- PYME/IO/FileUtils/SPAD.py +54 -0
- PYME/IO/FileUtils/TiffSeqToHdf5.py +87 -0
- PYME/IO/FileUtils/__init__.py +24 -0
- PYME/IO/FileUtils/compHdf.py +39 -0
- PYME/IO/FileUtils/fileID.py +246 -0
- PYME/IO/FileUtils/fnlGen.py +40 -0
- PYME/IO/FileUtils/freeSpace.py +59 -0
- PYME/IO/FileUtils/h5-thumbnailer.py +85 -0
- PYME/IO/FileUtils/h5ExFrames.py +98 -0
- PYME/IO/FileUtils/h5ToTiffSeries.py +50 -0
- PYME/IO/FileUtils/h5r-thumbnailer.py +118 -0
- PYME/IO/FileUtils/h5rFormatDef.py +99 -0
- PYME/IO/FileUtils/h5rToTxt.py +77 -0
- PYME/IO/FileUtils/nameUtils.py +300 -0
- PYME/IO/FileUtils/obf_support.py +538 -0
- PYME/IO/FileUtils/ply.py +147 -0
- PYME/IO/FileUtils/readSpeckle.py +78 -0
- PYME/IO/FileUtils/readTiff.py +64 -0
- PYME/IO/FileUtils/saveTiffStack.py +137 -0
- PYME/IO/FileUtils/setup.py +36 -0
- PYME/IO/FileUtils/sf-thumbnailer.py +76 -0
- PYME/IO/FileUtils/stl.py +44 -0
- PYME/IO/FileUtils/thumbnailDatabase.py +104 -0
- PYME/IO/HTTPSpooler_v2.py +141 -0
- PYME/IO/MetaDataHandler.py +1072 -0
- PYME/IO/PZFFormat.py +375 -0
- PYME/IO/Spooler.py +279 -0
- PYME/IO/__init__.py +12 -0
- PYME/IO/acquisition_backends.py +353 -0
- PYME/IO/buffer_helpers.cp39-win_amd64.dll.a +0 -0
- PYME/IO/buffer_helpers.cp39-win_amd64.pyd +0 -0
- PYME/IO/buffer_helpers.pyx +190 -0
- PYME/IO/buffers.py +291 -0
- PYME/IO/clusterDuplication.py +204 -0
- PYME/IO/clusterExport.py +152 -0
- PYME/IO/clusterGlob.py +108 -0
- PYME/IO/clusterIO.py +1076 -0
- PYME/IO/clusterListing.py +232 -0
- PYME/IO/clusterResults.py +206 -0
- PYME/IO/clusterUpload.py +49 -0
- PYME/IO/cluster_directory.py +419 -0
- PYME/IO/cluster_streaming.py +216 -0
- PYME/IO/compatibility.py +12 -0
- PYME/IO/countdir/__init__.py +5 -0
- PYME/IO/countdir/setup.py +58 -0
- PYME/IO/csv_flavours.py +251 -0
- PYME/IO/dataExporter.py +470 -0
- PYME/IO/dataWrap.py +108 -0
- PYME/IO/dcimg.py +167 -0
- PYME/IO/events.py +161 -0
- PYME/IO/h5File.py +148 -0
- PYME/IO/h5rFile.py +323 -0
- PYME/IO/image.py +1447 -0
- PYME/IO/load_psf.py +26 -0
- PYME/IO/pyme_h5ls.py +58 -0
- PYME/IO/ragged.py +138 -0
- PYME/IO/rgb_image.py +94 -0
- PYME/IO/tabular.py +1338 -0
- PYME/IO/testClusterSpooling.py +199 -0
- PYME/IO/unifiedIO.py +301 -0
- PYME/LMVis/Extras/QPobjectSegment.py +220 -0
- PYME/LMVis/Extras/__init__.py +55 -0
- PYME/LMVis/Extras/animation.py +359 -0
- PYME/LMVis/Extras/annotation.py +78 -0
- PYME/LMVis/Extras/clusterAnalysis.py +460 -0
- PYME/LMVis/Extras/dockedPanel.py +69 -0
- PYME/LMVis/Extras/extra_layers.py +373 -0
- PYME/LMVis/Extras/fiducial.py +230 -0
- PYME/LMVis/Extras/flagZMotionArtifacts.py +60 -0
- PYME/LMVis/Extras/fusionUtils.py +9 -0
- PYME/LMVis/Extras/multiColourAnalysis.py +103 -0
- PYME/LMVis/Extras/multiviewMapping.py +489 -0
- PYME/LMVis/Extras/objectMeasurements.py +246 -0
- PYME/LMVis/Extras/particleTracking.py +272 -0
- PYME/LMVis/Extras/perFrameVariable.py +77 -0
- PYME/LMVis/Extras/photophysics.py +72 -0
- PYME/LMVis/Extras/pointSetGeneration.py +232 -0
- PYME/LMVis/Extras/pointwiseColoc.py +54 -0
- PYME/LMVis/Extras/saved_views.py +125 -0
- PYME/LMVis/Extras/scaleBox.py +115 -0
- PYME/LMVis/Extras/sequentialSpecies.py +125 -0
- PYME/LMVis/Extras/setBGImage.py +74 -0
- PYME/LMVis/Extras/shiftmapGenerator.py +138 -0
- PYME/LMVis/Extras/showShortcuts.py +69 -0
- PYME/LMVis/Extras/snapshot.py +135 -0
- PYME/LMVis/Extras/spherical_harmonics.py +98 -0
- PYME/LMVis/Extras/subsets.py +15 -0
- PYME/LMVis/Extras/surface_fitting.py +52 -0
- PYME/LMVis/Extras/temperaturePlot.py +53 -0
- PYME/LMVis/Extras/vibration.py +112 -0
- PYME/LMVis/VisGUI.py +506 -0
- PYME/LMVis/__init__.py +39 -0
- PYME/LMVis/colourFilterGUI.py +77 -0
- PYME/LMVis/colourPanel.py +491 -0
- PYME/LMVis/displayPane.py +462 -0
- PYME/LMVis/dyeRatios.py +116 -0
- PYME/LMVis/genImageDialog.py +298 -0
- PYME/LMVis/gl_offScreenHandler.py +134 -0
- PYME/LMVis/gl_render.py +1302 -0
- PYME/LMVis/gl_render3D.py +1453 -0
- PYME/LMVis/gl_render3D_shaders.py +1213 -0
- PYME/LMVis/glcanvas_core.py +1157 -0
- PYME/LMVis/imageView.py +752 -0
- PYME/LMVis/imageView2.py +372 -0
- PYME/LMVis/importTextDialog.py +342 -0
- PYME/LMVis/layer_panel.py +231 -0
- PYME/LMVis/layers/AxesOverlayLayer.py +126 -0
- PYME/LMVis/layers/LUTOverlayLayer.py +346 -0
- PYME/LMVis/layers/OverlayLayer.py +46 -0
- PYME/LMVis/layers/ScaleBarOverlayLayer.py +97 -0
- PYME/LMVis/layers/ScaleBoxOverlayLayer.py +181 -0
- PYME/LMVis/layers/SelectionOverlayLayer.py +124 -0
- PYME/LMVis/layers/__init__.py +53 -0
- PYME/LMVis/layers/base.py +319 -0
- PYME/LMVis/layers/image_layer.py +363 -0
- PYME/LMVis/layers/labels.py +186 -0
- PYME/LMVis/layers/layer_defaults.py +97 -0
- PYME/LMVis/layers/mesh.py +378 -0
- PYME/LMVis/layers/octree.py +194 -0
- PYME/LMVis/layers/pointcloud.py +372 -0
- PYME/LMVis/layers/quiver.py +271 -0
- PYME/LMVis/layers/text.py +250 -0
- PYME/LMVis/layers/tracks.py +309 -0
- PYME/LMVis/mv_math.py +78 -0
- PYME/LMVis/objectDarkMeasure.py +120 -0
- PYME/LMVis/pipeline.py +1227 -0
- PYME/LMVis/pipelineView.py +115 -0
- PYME/LMVis/pointSettingsPanel.py +135 -0
- PYME/LMVis/progGraph.py +59 -0
- PYME/LMVis/quadTreeSettings.py +90 -0
- PYME/LMVis/rendGauss.py +158 -0
- PYME/LMVis/renderers.py +576 -0
- PYME/LMVis/sessionpaths.py +136 -0
- PYME/LMVis/setup.py +39 -0
- PYME/LMVis/setup_exe.py +71 -0
- PYME/LMVis/shader_programs/DefaultShaderProgram.py +149 -0
- PYME/LMVis/shader_programs/GLProgram.py +119 -0
- PYME/LMVis/shader_programs/GouraudShaderProgram.py +157 -0
- PYME/LMVis/shader_programs/PointSpriteShaderProgram.py +163 -0
- PYME/LMVis/shader_programs/ShaderProgramFactory.py +54 -0
- PYME/LMVis/shader_programs/TesselShaderProgram.py +47 -0
- PYME/LMVis/shader_programs/WireFrameShaderProgram.py +50 -0
- PYME/LMVis/shader_programs/__init__.py +20 -0
- PYME/LMVis/shader_programs/oit_compositor.py +23 -0
- PYME/LMVis/shader_programs/shader_loader.py +84 -0
- PYME/LMVis/shader_programs/shader_program.py +168 -0
- PYME/LMVis/shader_programs/shaders/bigflatpoints_fs.glsl +16 -0
- PYME/LMVis/shader_programs/shaders/bigpoints_gs.glsl +40 -0
- PYME/LMVis/shader_programs/shaders/bigpointsprites_fs.glsl +20 -0
- PYME/LMVis/shader_programs/shaders/bigspheres_fs.glsl +53 -0
- PYME/LMVis/shader_programs/shaders/compose_fs.glsl +41 -0
- PYME/LMVis/shader_programs/shaders/compose_vs.glsl +25 -0
- PYME/LMVis/shader_programs/shaders/default_fs.glsl +27 -0
- PYME/LMVis/shader_programs/shaders/default_vs.glsl +88 -0
- PYME/LMVis/shader_programs/shaders/flatpoints_fs.glsl +27 -0
- PYME/LMVis/shader_programs/shaders/gouraud_flatpoints_fs.glsl +30 -0
- PYME/LMVis/shader_programs/shaders/gouraud_fs.glsl +26 -0
- PYME/LMVis/shader_programs/shaders/gouraud_oit_fs.glsl +68 -0
- PYME/LMVis/shader_programs/shaders/gouraud_vs.glsl +135 -0
- PYME/LMVis/shader_programs/shaders/image_fs.glsl +60 -0
- PYME/LMVis/shader_programs/shaders/image_vs.glsl +79 -0
- PYME/LMVis/shader_programs/shaders/phong_fs.glsl +43 -0
- PYME/LMVis/shader_programs/shaders/phong_vs.glsl +45 -0
- PYME/LMVis/shader_programs/shaders/pointsprites_fs.glsl +33 -0
- PYME/LMVis/shader_programs/shaders/pointsprites_vs.glsl +77 -0
- PYME/LMVis/shader_programs/shaders/spheres_fs.glsl +102 -0
- PYME/LMVis/shader_programs/shaders/tessel_fs.glsl +23 -0
- PYME/LMVis/shader_programs/shaders/tessel_vs.glsl +37 -0
- PYME/LMVis/shader_programs/shaders/text_fs.glsl +42 -0
- PYME/LMVis/shader_programs/shaders/text_vs.glsl +84 -0
- PYME/LMVis/shader_programs/shaders/widelines_fs.glsl +19 -0
- PYME/LMVis/shader_programs/shaders/widelines_gs.glsl +56 -0
- PYME/LMVis/shader_programs/shaders/widelines_vs.glsl +51 -0
- PYME/LMVis/statusLog.py +57 -0
- PYME/LMVis/triBlobs.py +142 -0
- PYME/LMVis/view_clipping_pane.py +429 -0
- PYME/LMVis/views.py +275 -0
- PYME/LMVis/visCore.py +1005 -0
- PYME/LMVis/visHelpers.py +880 -0
- PYME/LMVis/visHelpersMin.py +230 -0
- PYME/LMVis/workspaceTree.py +238 -0
- PYME/__init__.py +22 -0
- PYME/cluster/HTTPDataServer.py +1063 -0
- PYME/cluster/HTTPRulePusher.py +382 -0
- PYME/cluster/HTTPTaskPusher.py +377 -0
- PYME/cluster/PYMERuleNodeServer.py +192 -0
- PYME/cluster/PYMERuleServer.py +109 -0
- PYME/cluster/__init__.py +0 -0
- PYME/cluster/clusterUI/accounts/__init__.py +0 -0
- PYME/cluster/clusterUI/accounts/admin.py +85 -0
- PYME/cluster/clusterUI/accounts/forms.py +8 -0
- PYME/cluster/clusterUI/accounts/migrations/0001_initial.py +31 -0
- PYME/cluster/clusterUI/accounts/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/accounts/models.py +44 -0
- PYME/cluster/clusterUI/accounts/templates/accounts/user_form.html +13 -0
- PYME/cluster/clusterUI/accounts/templates/registration/initial_complete.html +6 -0
- PYME/cluster/clusterUI/accounts/templates/registration/initial_confirm.html +13 -0
- PYME/cluster/clusterUI/accounts/templates/registration/initial_done.html +7 -0
- PYME/cluster/clusterUI/accounts/templates/registration/verification.html +2 -0
- PYME/cluster/clusterUI/accounts/templates/registration/verification_subject.txt +1 -0
- PYME/cluster/clusterUI/accounts/urls.py +19 -0
- PYME/cluster/clusterUI/accounts/views.py +36 -0
- PYME/cluster/clusterUI/clusterUI/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterUI/settings.py +147 -0
- PYME/cluster/clusterUI/clusterUI/urls.py +31 -0
- PYME/cluster/clusterUI/clusterUI/wsgi.py +16 -0
- PYME/cluster/clusterUI/clusterbrowser/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterbrowser/admin.py +3 -0
- PYME/cluster/clusterUI/clusterbrowser/apps.py +7 -0
- PYME/cluster/clusterUI/clusterbrowser/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterbrowser/models.py +5 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/base.html +129 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/dirlisting.html +231 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/form_new_folder.html +18 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/form_upload_file.html +17 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/clusterbrowser/lightlisting.html +25 -0
- PYME/cluster/clusterUI/clusterbrowser/templates/registration/login.html +38 -0
- PYME/cluster/clusterUI/clusterbrowser/urls.py +10 -0
- PYME/cluster/clusterUI/clusterbrowser/views.py +148 -0
- PYME/cluster/clusterUI/clusterstatus/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterstatus/admin.py +3 -0
- PYME/cluster/clusterUI/clusterstatus/apps.py +7 -0
- PYME/cluster/clusterUI/clusterstatus/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/clusterstatus/models.py +5 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/load_content.html +29 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/queue_info.html +28 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/queue_info_content.html +50 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/status_dash.html +29 -0
- PYME/cluster/clusterUI/clusterstatus/templates/clusterstatus/status_dash_content.html +90 -0
- PYME/cluster/clusterUI/clusterstatus/urls.py +9 -0
- PYME/cluster/clusterUI/clusterstatus/views.py +69 -0
- PYME/cluster/clusterUI/clusterui_tornado.py +117 -0
- PYME/cluster/clusterUI/deploy.py +62 -0
- PYME/cluster/clusterUI/localization/__init__.py +0 -0
- PYME/cluster/clusterUI/localization/admin.py +3 -0
- PYME/cluster/clusterUI/localization/apps.py +7 -0
- PYME/cluster/clusterUI/localization/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/localization/models.py +5 -0
- PYME/cluster/clusterUI/localization/templates/localization/settings_form.html +67 -0
- PYME/cluster/clusterUI/localization/urls.py +10 -0
- PYME/cluster/clusterUI/localization/views.py +170 -0
- PYME/cluster/clusterUI/manage.py +17 -0
- PYME/cluster/clusterUI/recipes/__init__.py +0 -0
- PYME/cluster/clusterUI/recipes/admin.py +3 -0
- PYME/cluster/clusterUI/recipes/migrations/__init__.py +0 -0
- PYME/cluster/clusterUI/recipes/models.py +3 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/extra_inputs.html +35 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/form_recipe.html +82 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/input_list.html +7 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_editor.html +28 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_editor_common.html +46 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_editrun.html +93 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_standalone.html +100 -0
- PYME/cluster/clusterUI/recipes/templates/recipes/recipe_template.html +109 -0
- PYME/cluster/clusterUI/recipes/urls.py +17 -0
- PYME/cluster/clusterUI/recipes/views.py +109 -0
- PYME/cluster/cluster_of_one.py +200 -0
- PYME/cluster/dataserver_wsgi.py +615 -0
- PYME/cluster/distribution.py +71 -0
- PYME/cluster/rulenodeserver.py +487 -0
- PYME/cluster/rules.py +979 -0
- PYME/cluster/ruleserver.py +1106 -0
- PYME/cluster/status.py +101 -0
- PYME/cluster/taskWorkerHTTP.py +522 -0
- PYME/cluster/webdav.py +1102 -0
- PYME/config.py +659 -0
- PYME/contrib/TextCtrlAutoComplete.py +582 -0
- PYME/contrib/__init__.py +7 -0
- PYME/contrib/aniso/__init__.py +2 -0
- PYME/contrib/aniso/aniso.py +268 -0
- PYME/contrib/auiFloatBook.py +94 -0
- PYME/contrib/congrid.py +127 -0
- PYME/contrib/dispatch/__init__.py +19 -0
- PYME/contrib/dispatch/dispatcher.py +289 -0
- PYME/contrib/gohlke/__init__.py +1 -0
- PYME/contrib/gohlke/_tifffile.cp39-win_amd64.dll.a +0 -0
- PYME/contrib/gohlke/_tifffile.cp39-win_amd64.pyd +0 -0
- PYME/contrib/gohlke/czifile.py +1289 -0
- PYME/contrib/gohlke/fcsfiles.py +514 -0
- PYME/contrib/gohlke/setup.py +54 -0
- PYME/contrib/gohlke/tifffile.py +3095 -0
- PYME/contrib/listctrlMixins.py +896 -0
- PYME/contrib/lzw.py +838 -0
- PYME/contrib/pad/__init__.py +13 -0
- PYME/contrib/pad/info_pad.py +18 -0
- PYME/contrib/pad/pad.py +670 -0
- PYME/contrib/setup.py +38 -0
- PYME/contrib/wxPlotPanel.py +137 -0
- PYME/experimental/SimReconstruction.py +49 -0
- PYME/experimental/__init__.pxd +0 -0
- PYME/experimental/__init__.py +4 -0
- PYME/experimental/_octree.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/_octree.cp39-win_amd64.pyd +0 -0
- PYME/experimental/_octree.pyx +477 -0
- PYME/experimental/_treap.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/_treap.cp39-win_amd64.pyd +0 -0
- PYME/experimental/_treap.pyx +267 -0
- PYME/experimental/_triangle_mesh.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/_triangle_mesh.cp39-win_amd64.pyd +0 -0
- PYME/experimental/_triangle_mesh.pxd +149 -0
- PYME/experimental/_triangle_mesh.pyx +2787 -0
- PYME/experimental/aqavg.py +40 -0
- PYME/experimental/dcimgFileChucker.py +375 -0
- PYME/experimental/dcimgSpoolShim.py +174 -0
- PYME/experimental/dcimg_chuck_test.py +78 -0
- PYME/experimental/dual_marching_cubes.py +801 -0
- PYME/experimental/dual_marching_cubes_v2.py +645 -0
- PYME/experimental/expectationMaximisation.py +298 -0
- PYME/experimental/func_octree.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/func_octree.cp39-win_amd64.pyd +0 -0
- PYME/experimental/func_octree.pyx +381 -0
- PYME/experimental/fusionRadial.py +520 -0
- PYME/experimental/fusionUtils.py +522 -0
- PYME/experimental/isosurface.py +147 -0
- PYME/experimental/labview_spooling_hacks.py +48 -0
- PYME/experimental/marching_cubes.py +811 -0
- PYME/experimental/marching_tetrahedra.py +343 -0
- PYME/experimental/modified_marching_cubes.py +1245 -0
- PYME/experimental/music.py +25 -0
- PYME/experimental/octree.py +188 -0
- PYME/experimental/pSpaceMap.py +34 -0
- PYME/experimental/pointFitConfoc.py +112 -0
- PYME/experimental/scanner_control.py +107 -0
- PYME/experimental/setup.py +109 -0
- PYME/experimental/spark.py +31 -0
- PYME/experimental/tcHist.py +71 -0
- PYME/experimental/treap.py +262 -0
- PYME/experimental/triangle_mesh.py +2287 -0
- PYME/experimental/triangle_mesh_utils.cp39-win_amd64.dll.a +0 -0
- PYME/experimental/triangle_mesh_utils.cp39-win_amd64.pyd +0 -0
- PYME/experimental/triangle_utils.py +87 -0
- PYME/experimental/triangular_mesh.py +2255 -0
- PYME/experimental/zMotionArtifactUtils.py +55 -0
- PYME/localization/FitFactories/AstigGaussFitFR.py +175 -0
- PYME/localization/FitFactories/AstigGaussGPUFitFR.py +326 -0
- PYME/localization/FitFactories/BeadConvolvedAstigGaussFit.py +206 -0
- PYME/localization/FitFactories/ChecksumFR.py +82 -0
- PYME/localization/FitFactories/ChecksumGPUFR.py +213 -0
- PYME/localization/FitFactories/ChecksumMultifitSR.py +86 -0
- PYME/localization/FitFactories/ConfocCOIR.py +93 -0
- PYME/localization/FitFactories/Dumbell3DFitR.py +193 -0
- PYME/localization/FitFactories/DumbellFitR.py +163 -0
- PYME/localization/FitFactories/FFBase.py +403 -0
- PYME/localization/FitFactories/Gauss3DFitR.py +213 -0
- PYME/localization/FitFactories/GaussFitConstrR.py +177 -0
- PYME/localization/FitFactories/GaussMultifitLinesR.py +351 -0
- PYME/localization/FitFactories/GaussMultifitR.py +326 -0
- PYME/localization/FitFactories/GaussMultifitSR.py +373 -0
- PYME/localization/FitFactories/InterpFitR.py +236 -0
- PYME/localization/FitFactories/InterpFitWFR.py +111 -0
- PYME/localization/FitFactories/Interpolators/CSInterpolator.py +148 -0
- PYME/localization/FitFactories/Interpolators/CubeSInterpolatorP.py +121 -0
- PYME/localization/FitFactories/Interpolators/CubicSplineInterpolator.py +69 -0
- PYME/localization/FitFactories/Interpolators/LinearInterpolator.py +135 -0
- PYME/localization/FitFactories/Interpolators/LinearPInterpolatorP.py +95 -0
- PYME/localization/FitFactories/Interpolators/__init__.py +33 -0
- PYME/localization/FitFactories/Interpolators/baseInterpolator.py +202 -0
- PYME/localization/FitFactories/LatFitCOIR.py +82 -0
- PYME/localization/FitFactories/LatGaussFitFR.py +220 -0
- PYME/localization/FitFactories/LatObjFindFR.py +85 -0
- PYME/localization/FitFactories/LatPSFFitR.py +142 -0
- PYME/localization/FitFactories/MultiviewFitInterpNR.py +400 -0
- PYME/localization/FitFactories/PRInterpFitQR.py +192 -0
- PYME/localization/FitFactories/PRInterpFitR.py +223 -0
- PYME/localization/FitFactories/ROIExtractNR.py +93 -0
- PYME/localization/FitFactories/SpecGaussFitFR.py +174 -0
- PYME/localization/FitFactories/SplitterFitCOIR.py +142 -0
- PYME/localization/FitFactories/SplitterFitFNR.py +312 -0
- PYME/localization/FitFactories/SplitterFitFusionR.py +318 -0
- PYME/localization/FitFactories/SplitterFitInterpBNR.py +357 -0
- PYME/localization/FitFactories/SplitterFitInterpNR.py +343 -0
- PYME/localization/FitFactories/SplitterFitScavNR.py +352 -0
- PYME/localization/FitFactories/SplitterMultifitSR.py +410 -0
- PYME/localization/FitFactories/SplitterObjFindR.py +194 -0
- PYME/localization/FitFactories/SplitterROIExtractNR.py +91 -0
- PYME/localization/FitFactories/SplitterShiftEstFR.py +230 -0
- PYME/localization/FitFactories/__init__.py +103 -0
- PYME/localization/FitFactories/fitCommon.py +195 -0
- PYME/localization/FitFactories/zEstimators/__init__.py +29 -0
- PYME/localization/FitFactories/zEstimators/astigEstimator.py +173 -0
- PYME/localization/FitFactories/zEstimators/biplaneEstimator.py +209 -0
- PYME/localization/FitFactories/zEstimators/genericDualEstimator.py +232 -0
- PYME/localization/FitFactories/zEstimators/genericEstimator.py +224 -0
- PYME/localization/FitFactories/zEstimators/priEstimator.py +255 -0
- PYME/localization/MetaDataEdit.py +543 -0
- PYME/localization/Test/30_9_series_A_1.sf +0 -0
- PYME/localization/Test/PRI.md +45 -0
- PYME/localization/Test/PRI_qr.md +52 -0
- PYME/localization/Test/PRI_r.md +52 -0
- PYME/localization/Test/__init__.py +22 -0
- PYME/localization/Test/astig.md +50 -0
- PYME/localization/Test/astigCOI.md +50 -0
- PYME/localization/Test/astig_gauss.md +52 -0
- PYME/localization/Test/astig_pe.md +50 -0
- PYME/localization/Test/astig_splitter.md +66 -0
- PYME/localization/Test/astig_splitterCOI.md +67 -0
- PYME/localization/Test/astig_splitterQ.md +66 -0
- PYME/localization/Test/astig_theory.md +50 -0
- PYME/localization/Test/astig_theory.psf +0 -0
- PYME/localization/Test/astig_theory.tif +0 -0
- PYME/localization/Test/beads1.psf +0 -0
- PYME/localization/Test/beads1.tif +0 -0
- PYME/localization/Test/biplane_splitter.md +75 -0
- PYME/localization/Test/biplane_splitter2.md +103 -0
- PYME/localization/Test/biplane_splitter3.md +115 -0
- PYME/localization/Test/defoc.md +48 -0
- PYME/localization/Test/dumbell_3d.md +52 -0
- PYME/localization/Test/fitTestJig.py +172 -0
- PYME/localization/Test/fitTestJigSCMOS.py +487 -0
- PYME/localization/Test/fitTestJigWC.py +225 -0
- PYME/localization/Test/gauss.md +50 -0
- PYME/localization/Test/gaussCOI.md +50 -0
- PYME/localization/Test/pri_pe.md +50 -0
- PYME/localization/Test/pri_theory.md +51 -0
- PYME/localization/Test/pri_theory.psf +0 -0
- PYME/localization/Test/pri_theory.tif +0 -0
- PYME/localization/Test/psf_astig.psf +0 -0
- PYME/localization/Test/psf_astig.tif +0 -0
- PYME/localization/Test/psf_astig_bg_d.psf +0 -0
- PYME/localization/Test/psf_astig_bg_d.tif +0 -0
- PYME/localization/Test/psf_astig_pe.psf +0 -0
- PYME/localization/Test/psf_astig_pe.tif +0 -0
- PYME/localization/Test/psf_decon.psf +0 -0
- PYME/localization/Test/psf_decon.tif +0 -0
- PYME/localization/Test/psf_decon_pe.psf +0 -0
- PYME/localization/Test/psf_decon_pe.tif +0 -0
- PYME/localization/Test/psf_guess.psf +0 -0
- PYME/localization/Test/splitter.md +63 -0
- PYME/localization/Test/splitter_scav.md +63 -0
- PYME/localization/Test/test.psf +0 -0
- PYME/localization/Test/test.tif +0 -0
- PYME/localization/Test/test_decon.psf +0 -0
- PYME/localization/Test/test_decon.tif +0 -0
- PYME/localization/Test/test_pe.psf +0 -0
- PYME/localization/Test/test_pe.tif +0 -0
- PYME/localization/Test/wf_theory.psf +0 -0
- PYME/localization/Test/wf_theory2.psf +0 -0
- PYME/localization/__init__.py +17 -0
- PYME/localization/autopsf/__init__.py +1 -0
- PYME/localization/autopsf/autopsf.py +486 -0
- PYME/localization/autopsf/roifit.py +310 -0
- PYME/localization/cInterp/__init__.py +24 -0
- PYME/localization/cInterp/cInterp.cp39-win_amd64.dll.a +0 -0
- PYME/localization/cInterp/cInterp.cp39-win_amd64.pyd +0 -0
- PYME/localization/cInterp/setup.py +54 -0
- PYME/localization/cModels/__init__.py +24 -0
- PYME/localization/cModels/gauss_app.cp39-win_amd64.dll.a +0 -0
- PYME/localization/cModels/gauss_app.cp39-win_amd64.pyd +0 -0
- PYME/localization/ofind.py +444 -0
- PYME/localization/ofind3d.py +306 -0
- PYME/localization/ofind_nofilt.py +128 -0
- PYME/localization/ofind_pri.py +478 -0
- PYME/localization/ofind_split.py +399 -0
- PYME/localization/ofind_xcorr.py +351 -0
- PYME/localization/ofind_xcorr_fw.py +441 -0
- PYME/localization/remFitBuf.py +657 -0
- PYME/localization/setup.py +56 -0
- PYME/localization/splitting.py +240 -0
- PYME/localization/twist.py +60 -0
- PYME/misc/TiSettings.py +36 -0
- PYME/misc/TiffImagePlugin.py +838 -0
- PYME/misc/VolProj.py +144 -0
- PYME/misc/__init__.py +25 -0
- PYME/misc/aligned_array.py +10 -0
- PYME/misc/aniso.py +29 -0
- PYME/misc/big_sur_fix.py +20 -0
- PYME/misc/check_for_updates.py +139 -0
- PYME/misc/colormaps.py +199 -0
- PYME/misc/colorsys.py +201 -0
- PYME/misc/computerName.py +33 -0
- PYME/misc/coverslipGeometries.py +60 -0
- PYME/misc/cython_numpy_monkey.py +40 -0
- PYME/misc/depGraph.py +406 -0
- PYME/misc/dirSize.py +31 -0
- PYME/misc/diskCleanup.py +64 -0
- PYME/misc/djangoRecarray.py +57 -0
- PYME/misc/django_widgets.py +27 -0
- PYME/misc/editgrid.py +101 -0
- PYME/misc/eventSpy.py +122 -0
- PYME/misc/exceptions.py +30 -0
- PYME/misc/facsPlot.py +89 -0
- PYME/misc/fbpIcons.py +90 -0
- PYME/misc/fftw_compat.py +28 -0
- PYME/misc/findCycles.py +91 -0
- PYME/misc/fortran_interrupt_defeat.py +43 -0
- PYME/misc/glassModel.py +64 -0
- PYME/misc/hash32.py +41 -0
- PYME/misc/hillcurve.py +88 -0
- PYME/misc/hybrid_ns.py +84 -0
- PYME/misc/kdFit.py +72 -0
- PYME/misc/locate.py +10 -0
- PYME/misc/memUsage.py +65 -0
- PYME/misc/memmap.py +52 -0
- PYME/misc/mock_ctypes.py +28 -0
- PYME/misc/mock_traits.py +59 -0
- PYME/misc/mock_traitsui.py +29 -0
- PYME/misc/pyme_zeroconf.py +287 -0
- PYME/misc/pyro_tracebacks.py +32 -0
- PYME/misc/read_agf.py +605 -0
- PYME/misc/sffake.py +16 -0
- PYME/misc/snells.py +71 -0
- PYME/misc/sqlite_ns.py +132 -0
- PYME/misc/sqlitendarray.py +56 -0
- PYME/misc/taskDef.py +44 -0
- PYME/misc/tempDB.py +56 -0
- PYME/misc/wxThread.py +33 -0
- PYME/misc/zemax.py +259 -0
- PYME/misc/zernike.py +151 -0
- PYME/pyme_warnings.py +70 -0
- PYME/recipes/Recipes/STEDDeconvolution.yaml +14 -0
- PYME/recipes/Recipes/STEDMinistacks.yaml +15 -0
- PYME/recipes/Recipes/STED_COP3.yaml +25 -0
- PYME/recipes/Recipes/blobMeas.yaml +19 -0
- PYME/recipes/Recipes/blobMeas2.yaml +37 -0
- PYME/recipes/Recipes/blobMeasSR.yaml +66 -0
- PYME/recipes/Recipes/coloc.yaml +43 -0
- PYME/recipes/Recipes/coloc_roi_masked.yaml +46 -0
- PYME/recipes/Recipes/multiblob.yaml +38 -0
- PYME/recipes/Recipes/pre_particleTracking.yaml +30 -0
- PYME/recipes/Recipes/segmentAndMeasureLoci.yaml +41 -0
- PYME/recipes/__init__.py +6 -0
- PYME/recipes/acquisition.py +158 -0
- PYME/recipes/bakeshop.py +32 -0
- PYME/recipes/base.py +1393 -0
- PYME/recipes/batchProcess.py +147 -0
- PYME/recipes/filters.py +394 -0
- PYME/recipes/fudges.py +41 -0
- PYME/recipes/graphing.py +108 -0
- PYME/recipes/html_recipe_editor.py +90 -0
- PYME/recipes/localisations.py +1386 -0
- PYME/recipes/machine_learning.py +286 -0
- PYME/recipes/measurement.py +1062 -0
- PYME/recipes/modules.py +50 -0
- PYME/recipes/multiview.py +435 -0
- PYME/recipes/output.py +667 -0
- PYME/recipes/pointcloud.py +510 -0
- PYME/recipes/processing.py +2954 -0
- PYME/recipes/pyramid.py +206 -0
- PYME/recipes/recipe.py +965 -0
- PYME/recipes/recipeGui.py +1029 -0
- PYME/recipes/recipeLayout.py +499 -0
- PYME/recipes/runRecipe.py +140 -0
- PYME/recipes/setup.py +37 -0
- PYME/recipes/simulation.py +91 -0
- PYME/recipes/skfilters.py +143 -0
- PYME/recipes/surface_fitting.py +776 -0
- PYME/recipes/tablefilters.py +275 -0
- PYME/recipes/tracking.py +343 -0
- PYME/recipes/traits.py +157 -0
- PYME/recipes/vertical_recipe_display.py +373 -0
- PYME/resources/__init__.py +23 -0
- PYME/resources/config_template/distributor.yaml +14 -0
- PYME/resources/config_template/nodeserver.yaml +15 -0
- PYME/resources/icons/LUT.png +0 -0
- PYME/resources/icons/YSM_Shield.png +0 -0
- PYME/resources/icons/YSM_logo.png +0 -0
- PYME/resources/icons/arrow.png +0 -0
- PYME/resources/icons/crosshairs.png +0 -0
- PYME/resources/icons/line_select.png +0 -0
- PYME/resources/icons/logo_fmhs.png +0 -0
- PYME/resources/icons/media-playback-pause.png +0 -0
- PYME/resources/icons/media-playback-start.png +0 -0
- PYME/resources/icons/media-skip-backward.png +0 -0
- PYME/resources/icons/media-skip-forward.png +0 -0
- PYME/resources/icons/mesh.png +0 -0
- PYME/resources/icons/mesh.svg +28 -0
- PYME/resources/icons/pmacquire.icns +0 -0
- PYME/resources/icons/pmacquire.ico +0 -0
- PYME/resources/icons/pmanal.icns +0 -0
- PYME/resources/icons/pmanal.ico +0 -0
- PYME/resources/icons/pmvis.icns +0 -0
- PYME/resources/icons/pmvis.ico +0 -0
- PYME/resources/icons/points.png +0 -0
- PYME/resources/icons/points.svg +16 -0
- PYME/resources/icons/pymeLogo.png +0 -0
- PYME/resources/icons/rect_select.png +0 -0
- PYME/resources/icons/squiggle_select.png +0 -0
- PYME/resources/icons/text3800.icns +0 -0
- PYME/resources/icons/text3800.ico +0 -0
- PYME/resources/icons/text3800.png +0 -0
- PYME/resources/icons/text3800.tif +0 -0
- PYME/resources/icons/text5377.icns +0 -0
- PYME/resources/icons/text5377.ico +0 -0
- PYME/resources/icons/text5377.png +0 -0
- PYME/resources/icons/tracks.png +0 -0
- PYME/resources/icons/tracks.svg +19 -0
- PYME/resources/icons/view-front.png +0 -0
- PYME/resources/icons/view-home.png +0 -0
- PYME/resources/icons/view-right.png +0 -0
- PYME/resources/icons/view-top.png +0 -0
- PYME/resources/test_datasets/README.md +8 -0
- PYME/resources/test_datasets/t_im.tif +0 -0
- PYME/resources/test_datasets/test_csv.csv +4282 -0
- PYME/resources/test_datasets/test_hdf.hdf +0 -0
- PYME/resources/web/rule_inline.html +21 -0
- PYME/resources/web/ruleserver.html +75 -0
- PYME/resources/web/static/bootstrap.min.js +7 -0
- PYME/resources/web/static/cluster.css +137 -0
- PYME/resources/web/static/css/bootstrap-theme.css +587 -0
- PYME/resources/web/static/css/bootstrap-theme.min.css +6 -0
- PYME/resources/web/static/css/bootstrap.css +6757 -0
- PYME/resources/web/static/css/bootstrap.min.css +6 -0
- PYME/resources/web/static/css/flat_design_colors_full.css +306 -0
- PYME/resources/web/static/css/recipe_editor.css +106 -0
- PYME/resources/web/static/dashboard.css +106 -0
- PYME/resources/web/static/fileselector.js +77 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.eot +0 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.svg +288 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.ttf +0 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.woff +0 -0
- PYME/resources/web/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
- PYME/resources/web/static/images/osd_flat/LICENSE.txt +116 -0
- PYME/resources/web/static/images/osd_flat/README.md +9 -0
- PYME/resources/web/static/images/osd_flat/fullpage_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/fullpage_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/fullpage_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/fullpage_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/home_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/next_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/previous_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateleft_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/rotateright_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomin_rest.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_grouphover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_hover.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_pressed.png +0 -0
- PYME/resources/web/static/images/osd_flat/zoomout_rest.png +0 -0
- PYME/resources/web/static/jigna-vue-bare.js +1395 -0
- PYME/resources/web/static/jquery.min.js +2 -0
- PYME/resources/web/static/jstree.min.js +6 -0
- PYME/resources/web/static/openSeadragonGL.js +102 -0
- PYME/resources/web/static/openseadragon-svg-overlay.js +103 -0
- PYME/resources/web/static/openseadragon-viewerinputhook.min.js +6 -0
- PYME/resources/web/static/openseadragon.min.js +9 -0
- PYME/resources/web/static/recipe_editor.js +119 -0
- PYME/resources/web/static/shaders/fragment/lut.glsl +20 -0
- PYME/resources/web/static/shaders/fragment/none.glsl +17 -0
- PYME/resources/web/static/shaders/fragment/outline.glsl +43 -0
- PYME/resources/web/static/shaders/fragment/rect.glsl +7 -0
- PYME/resources/web/static/shaders/fragment/sobel3.glsl +71 -0
- PYME/resources/web/static/shaders/fragment/white.glsl +4 -0
- PYME/resources/web/static/shaders/vertex/rect.glsl +22 -0
- PYME/resources/web/static/shaders/vertex/square.glsl +9 -0
- PYME/resources/web/static/utils.js +3 -0
- PYME/resources/web/static/viaWebGL.js +217 -0
- PYME/setup.py +109 -0
- PYME/setup_exe.py +57 -0
- PYME/simulation/__init__.py +0 -0
- PYME/simulation/locify.py +319 -0
- PYME/simulation/pointsets.py +279 -0
- PYME/simulation/wormlike.py +99 -0
- PYME/simulation/wormlike2.py +198 -0
- PYME/simulation/wormlike3.py +197 -0
- PYME/simulation/wormlike_curled.py +104 -0
- PYME/tileviewer/__init__.py +0 -0
- PYME/tileviewer/templates/roi_list.html +12 -0
- PYME/tileviewer/templates/tileviewer.html +247 -0
- PYME/tileviewer/tileviewer.py +207 -0
- PYME/ui/AUIFrame.py +279 -0
- PYME/ui/MetadataTree.py +469 -0
- PYME/ui/__init__.py +4 -0
- PYME/ui/about_dlg.py +29 -0
- PYME/ui/autoFoldPanel.py +643 -0
- PYME/ui/cascading_layout.py +45 -0
- PYME/ui/crop_dialog.py +145 -0
- PYME/ui/custom_traits_editors.py +508 -0
- PYME/ui/editFilterDialog.py +80 -0
- PYME/ui/editList.py +40 -0
- PYME/ui/fastGraph.py +240 -0
- PYME/ui/filterPane.py +348 -0
- PYME/ui/histLimits.py +543 -0
- PYME/ui/layerFoldPanel.py +232 -0
- PYME/ui/manualFoldPanel.py +870 -0
- PYME/ui/mytimer.py +116 -0
- PYME/ui/patch_traitsui.py +151 -0
- PYME/ui/progress.py +160 -0
- PYME/ui/recArrayView.py +145 -0
- PYME/ui/selection.py +71 -0
- PYME/ui/shell.py +37 -0
- PYME/ui/traitsui_constants.py +91 -0
- PYME/ui/wx_compat.py +43 -0
- PYME/update_version.py +159 -0
- PYME/util/__init__.py +3 -0
- PYME/util/authenticate.py +164 -0
- PYME/util/execfile.py +21 -0
- PYME/util/fProfile/__init__.py +1 -0
- PYME/util/fProfile/convertProfile.py +117 -0
- PYME/util/fProfile/fProfile.py +162 -0
- PYME/util/fProfile/html/404.html +60 -0
- PYME/util/fProfile/html/css/main.css +258 -0
- PYME/util/fProfile/html/css/normalize.css +427 -0
- PYME/util/fProfile/html/css/normalize.min.css +1 -0
- PYME/util/fProfile/html/index.html +64 -0
- PYME/util/fProfile/html/js/main.js +492 -0
- PYME/util/fProfile/html/js/plugins.js +24 -0
- PYME/util/fProfile/html/js/vendor/crossfilter.min.js +1 -0
- PYME/util/fProfile/html/js/vendor/jquery-1.11.2.min.js +4 -0
- PYME/util/fProfile/html/js/vendor/modernizr-2.8.3.min.js +4 -0
- PYME/util/fProfile/html/js/vendor/tinycolor-min.js +4 -0
- PYME/util/fProfile/viewProfile.py +30 -0
- PYME/util/install_dependencies.py +95 -0
- PYME/util/log_verbosity.py +11 -0
- PYME/util/mProfile/__init__.py +1 -0
- PYME/util/mProfile/colorize_db_t.py +297 -0
- PYME/util/mProfile/mProfile.py +164 -0
- PYME/util/mProfile/p.py +101 -0
- PYME/util/mProfile/tProfile.py +165 -0
- PYME/util/pymelauncher.py +47 -0
- PYME/util/shmarray/__init__.py +0 -0
- PYME/util/shmarray/shmTest.py +74 -0
- PYME/util/shmarray/shmarray.py +165 -0
- PYME/util/webframework.py +365 -0
- PYME/version.py +65 -0
- python_microscopy-25.6.5.dist-info/METADATA +68 -0
- python_microscopy-25.6.5.dist-info/RECORD +1277 -0
- python_microscopy-25.6.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,2787 @@
|
|
|
1
|
+
# -*- coding: utf8 -*-
|
|
2
|
+
# cython: profile=True
|
|
3
|
+
from libc.stdint cimport int32_t
|
|
4
|
+
cimport numpy as np
|
|
5
|
+
import numpy as np
|
|
6
|
+
cimport cython
|
|
7
|
+
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
#from libc.math cimport sqrtf
|
|
11
|
+
|
|
12
|
+
cdef extern from "<math.h>" nogil:
|
|
13
|
+
float sqrtf(float)
|
|
14
|
+
|
|
15
|
+
from cpython.mem cimport PyMem_Malloc, PyMem_Realloc, PyMem_Free
|
|
16
|
+
|
|
17
|
+
import copy
|
|
18
|
+
|
|
19
|
+
from PYME.experimental import triangle_mesh_utils
|
|
20
|
+
|
|
21
|
+
DEF MAX_VERTEX_COUNT = 2**31
|
|
22
|
+
|
|
23
|
+
# Regular, boundary, and max vertex valences. These need tp be
|
|
24
|
+
DEF VALENCE = 6
|
|
25
|
+
DEF BOUNDARY_VALENCE = 4
|
|
26
|
+
|
|
27
|
+
cdef extern from "triangle_mesh_utils.c":
|
|
28
|
+
void _update_face_normals(int32_t *f_idxs, halfedge_t *halfedges, vertex_t *vertices, face_t *faces, signed int n_idxs)
|
|
29
|
+
|
|
30
|
+
void update_face_normal(int f_idx, halfedge_t *halfedges, vertex_d *vertices, face_d *faces)
|
|
31
|
+
void update_single_vertex_neighbours(int v_idx, halfedge_t *halfedges, vertex_d *vertices, face_d *faces)
|
|
32
|
+
|
|
33
|
+
int flood_fill_star_component(int32_t h_idx, int component, halfedge_t *halfedges)
|
|
34
|
+
float norm(const float *pos)
|
|
35
|
+
void cross(const float *a, const float *b, float *n)
|
|
36
|
+
void difference(const float *a, const float *b, float *out)
|
|
37
|
+
void vsum(const float *a, const float *b, float *out)
|
|
38
|
+
float dot(const float *a, const float *b)
|
|
39
|
+
void scalar_mult(float *a, const float b);
|
|
40
|
+
|
|
41
|
+
cdef extern from "remesh_ops.c":
|
|
42
|
+
int remesh_edge_flip(halfedge_t * halfedges, vertex_t *vertices, face_t * faces, int idx, int n_halfedges, int live_update);
|
|
43
|
+
int remesh_relax(halfedge_t *halfedges, vertex_t *vertices, int n_vertices, face_t* faces, int n_faces, float l, int n_iterations);
|
|
44
|
+
void remesh_edge_zipper(halfedge_t * halfedges, int32_t edge1, int32_t edge2);
|
|
45
|
+
int remesh_check_neighour_twins(halfedge_t * halfedges, vertex_t * vertices, int32_t vertex_id);
|
|
46
|
+
int remesh_edge_collapse(halfedge_t * halfedges, int32_t n_halfedges, vertex_t * vertices, face_t* faces, int32_t idx, int live_update);
|
|
47
|
+
int remesh_delete_vertex(vertex_t * vertices, int32_t v_idx);
|
|
48
|
+
int remesh_delete_edge(halfedge_t * halfedges, int32_t e_idx);
|
|
49
|
+
int remesh_delete_face(halfedge_t * halfedges, face_t * faces, int32_t e_idx);
|
|
50
|
+
int remesh_split_edge(halfedge_t * halfedges, int32_t n_halfedges, vertex_t * vertices, face_t* faces, int32_t idx, int32_t * new_edges,
|
|
51
|
+
int32_t *new_vertices, int32_t * new_faces, int n_edge_idx, int n_vertex_idx, int n_face_idx, int live_update, int upsample)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
HALFEDGE_DTYPE = np.dtype([('vertex', 'i4'), ('face', 'i4'), ('twin', 'i4'), ('next', 'i4'), ('prev', 'i4'), ('length', 'f4'), ('component', 'i4'), ('locally_manifold', 'i4')], align=True)
|
|
55
|
+
FACE_DTYPE = np.dtype([('halfedge', 'i4'), ('normal', '3f4'), ('area', 'f4'), ('component', 'i4')], align=True)
|
|
56
|
+
FACE_DTYPE2 = np.dtype([('halfedge', 'i4'), ('normal0', 'f4'), ('normal1', 'f4'), ('normal2', 'f4'), ('area', 'f4'), ('component', 'i4')], align=True)
|
|
57
|
+
# Note that VERTEX_DTYPE neighbors size must match NEIGHBORSIZE
|
|
58
|
+
VERTEX_DTYPE = np.dtype([('position', '3f4'), ('normal', '3f4'), ('halfedge', 'i4'), ('valence', 'i4'), ('neighbors', '20i4'), ('component', 'i4'),('locally_manifold', 'i4')], align=True)
|
|
59
|
+
VERTEX_DTYPE2 = np.dtype([('position0', 'f4'),
|
|
60
|
+
('position1', 'f4'),
|
|
61
|
+
('position2', 'f4'),
|
|
62
|
+
('normal0', 'f4'),
|
|
63
|
+
('normal1', 'f4'),
|
|
64
|
+
('normal2', 'f4'),
|
|
65
|
+
('halfedge', 'i4'),
|
|
66
|
+
('valence', 'i4'),
|
|
67
|
+
('neighbor0', 'i4'),
|
|
68
|
+
('neighbor1', 'i4'),
|
|
69
|
+
('neighbor2', 'i4'),
|
|
70
|
+
('neighbor3', 'i4'),
|
|
71
|
+
('neighbor4', 'i4'),
|
|
72
|
+
('neighbor5', 'i4'),
|
|
73
|
+
('neighbor6', 'i4'),
|
|
74
|
+
('neighbor7', 'i4'),
|
|
75
|
+
('neighbor8', 'i4'),
|
|
76
|
+
('neighbor9', 'i4'),
|
|
77
|
+
('neighbor10', 'i4'),
|
|
78
|
+
('neighbor11', 'i4'),
|
|
79
|
+
('neighbor12', 'i4'),
|
|
80
|
+
('neighbor13', 'i4'),
|
|
81
|
+
('neighbor14', 'i4'),
|
|
82
|
+
('neighbor15', 'i4'),
|
|
83
|
+
('neighbor16', 'i4'),
|
|
84
|
+
('neighbor17', 'i4'),
|
|
85
|
+
('neighbor18', 'i4'),
|
|
86
|
+
('neighbor19', 'i4'),
|
|
87
|
+
('component', 'i4'),
|
|
88
|
+
('locally_manifold', 'i4')], align=True)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# MESH CONVENTIONS
|
|
92
|
+
##################
|
|
93
|
+
#
|
|
94
|
+
# halfedge.vertex = vertex the halfedge is pointing TO
|
|
95
|
+
#
|
|
96
|
+
# vertex.halfedge = a halfedge that eminates FROM the vertex.
|
|
97
|
+
#
|
|
98
|
+
|
|
99
|
+
# cdef packed struct halfedge_d:
|
|
100
|
+
# int32_t vertex
|
|
101
|
+
# int32_t face
|
|
102
|
+
# int32_t twin
|
|
103
|
+
# int32_t next
|
|
104
|
+
# int32_t prev
|
|
105
|
+
# np.float32_t length
|
|
106
|
+
# int32_t component
|
|
107
|
+
#
|
|
108
|
+
# cdef packed struct face_d:
|
|
109
|
+
# int32_t halfedge
|
|
110
|
+
# np.float32_t normal0
|
|
111
|
+
# np.float32_t normal1
|
|
112
|
+
# np.float32_t normal2
|
|
113
|
+
# np.float32_t area
|
|
114
|
+
# int32_t component
|
|
115
|
+
|
|
116
|
+
# cdef packed struct face_t: #non flattened type from triangle_mesh_utils.h
|
|
117
|
+
# int32_t halfedge
|
|
118
|
+
# float normal[VECTORSIZE]
|
|
119
|
+
# float area
|
|
120
|
+
# int32_t component
|
|
121
|
+
|
|
122
|
+
# cdef packed struct vertex_d:
|
|
123
|
+
# np.float32_t position0
|
|
124
|
+
# np.float32_t position1
|
|
125
|
+
# np.float32_t position2
|
|
126
|
+
# np.float32_t normal0
|
|
127
|
+
# np.float32_t normal1
|
|
128
|
+
# np.float32_t normal2
|
|
129
|
+
# int32_t halfedge
|
|
130
|
+
# int32_t valence
|
|
131
|
+
# int32_t neighbor0
|
|
132
|
+
# int32_t neighbor1
|
|
133
|
+
# int32_t neighbor2
|
|
134
|
+
# int32_t neighbor3
|
|
135
|
+
# int32_t neighbor4
|
|
136
|
+
# int32_t neighbor5
|
|
137
|
+
# int32_t neighbor6
|
|
138
|
+
# int32_t neighbor7
|
|
139
|
+
# int32_t neighbor8
|
|
140
|
+
# int32_t neighbor9
|
|
141
|
+
# int32_t neighbor10
|
|
142
|
+
# int32_t neighbor11
|
|
143
|
+
# int32_t neighbor12
|
|
144
|
+
# int32_t neighbor13
|
|
145
|
+
# int32_t neighbor14
|
|
146
|
+
# int32_t neighbor15
|
|
147
|
+
# int32_t neighbor16
|
|
148
|
+
# int32_t neighbor17
|
|
149
|
+
# int32_t neighbor18
|
|
150
|
+
# int32_t neighbor19
|
|
151
|
+
# int32_t component
|
|
152
|
+
|
|
153
|
+
# cdef packed struct vertex_t: # non-flattened version from triangle_mesh_utils.c
|
|
154
|
+
# float position[VECTORSIZE];
|
|
155
|
+
# float normal[VECTORSIZE];
|
|
156
|
+
# int32_t halfedge;
|
|
157
|
+
# int32_t valence;
|
|
158
|
+
# int32_t neighbors[NEIGHBORSIZE];
|
|
159
|
+
# int32_t component;
|
|
160
|
+
|
|
161
|
+
#cdef union halfedge_d:
|
|
162
|
+
#ctypedef halfedge_t halfedge_d
|
|
163
|
+
#ctypedef face_t face_d
|
|
164
|
+
#ctypedef vertex_t vertex_d
|
|
165
|
+
|
|
166
|
+
LOOP_ALPHA_FACTOR = (np.log(13)-np.log(3))/12
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# string key arguments incur a lot of overhead - use index / enums
|
|
170
|
+
__insertion_keys = ['vertex', 'halfedge']
|
|
171
|
+
|
|
172
|
+
DEF INSERTION_KEY_VERTEX = 0
|
|
173
|
+
DEF INSERTION_KEY_HALFEDGE = 1
|
|
174
|
+
|
|
175
|
+
cdef class TrianglesBase(object):
|
|
176
|
+
"""
|
|
177
|
+
Base class to let VisGUI layers detect objects which are a bunch of triangles
|
|
178
|
+
|
|
179
|
+
a derived class should provide suitable attributes/properties for vertices, faces, face_normals,
|
|
180
|
+
and vertex_normals.
|
|
181
|
+
|
|
182
|
+
See Also: Tesselation class in PYME.recipes.pointcloud
|
|
183
|
+
|
|
184
|
+
TODO - move me out of here?
|
|
185
|
+
TODO - provide default implementations for face_normals and vertex_normals?
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
vertices=None
|
|
189
|
+
faces=None
|
|
190
|
+
face_normals=None
|
|
191
|
+
vertex_normals = None
|
|
192
|
+
|
|
193
|
+
def keys(self):
|
|
194
|
+
"""
|
|
195
|
+
returns the names of possible vertex attributes
|
|
196
|
+
"""
|
|
197
|
+
raise NotImplementedError('Over-ride in derived class')
|
|
198
|
+
|
|
199
|
+
def __getitem__(self, item):
|
|
200
|
+
"""
|
|
201
|
+
returns a given vertex attribute
|
|
202
|
+
"""
|
|
203
|
+
raise NotImplementedError('Over-ride in derived class')
|
|
204
|
+
|
|
205
|
+
cdef class TriangleMesh(TrianglesBase):
|
|
206
|
+
|
|
207
|
+
def __init__(self, vertices=None, faces=None, mesh=None, **kwargs):
|
|
208
|
+
"""
|
|
209
|
+
Base class for triangle meshes stored using halfedge data structure.
|
|
210
|
+
Expects STL-like input.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
vertices : np.array
|
|
215
|
+
N x 3 array of Euclidean points.
|
|
216
|
+
faces : np .array
|
|
217
|
+
M x 3 array of vertex indices indicating triangle connectivity.
|
|
218
|
+
Expects STL redundancy.
|
|
219
|
+
"""
|
|
220
|
+
if mesh is not None:
|
|
221
|
+
|
|
222
|
+
self._vertices = np.copy(mesh._vertices)
|
|
223
|
+
# Flatten to address cython view problem
|
|
224
|
+
_flat_vertices = self._vertices.view(VERTEX_DTYPE2)
|
|
225
|
+
# Set up c views to arrays
|
|
226
|
+
self._set_cvertices(_flat_vertices)
|
|
227
|
+
self._vertex_vacancies = copy.copy(mesh._vertex_vacancies)
|
|
228
|
+
|
|
229
|
+
self._faces = np.copy(mesh._faces)
|
|
230
|
+
_flat_faces = self._faces.view(FACE_DTYPE2)
|
|
231
|
+
# Set up c views to arrays
|
|
232
|
+
self._set_cfaces(_flat_faces)
|
|
233
|
+
self._face_vacancies = copy.copy(mesh._face_vacancies)
|
|
234
|
+
|
|
235
|
+
self._halfedges = np.copy(mesh._halfedges)
|
|
236
|
+
self._set_chalfedges(self._halfedges)
|
|
237
|
+
self._halfedge_vacancies = copy.copy(mesh._halfedge_vacancies)
|
|
238
|
+
else:
|
|
239
|
+
self.build_from_verts_faces(vertices, faces)
|
|
240
|
+
|
|
241
|
+
# Representation of faces by triplets of vertices
|
|
242
|
+
self._faces_by_vertex = None
|
|
243
|
+
self._components_valid = 0
|
|
244
|
+
self._H = None
|
|
245
|
+
self._K = None
|
|
246
|
+
|
|
247
|
+
# loop subdivision vars
|
|
248
|
+
self._loop_subdivision_flip_edges = []
|
|
249
|
+
self._loop_subdivision_new_vertices = []
|
|
250
|
+
|
|
251
|
+
# Singular edges
|
|
252
|
+
# self._singular_edges = None
|
|
253
|
+
self._singular_edges_valid = 0
|
|
254
|
+
# self._singular_vertices = None
|
|
255
|
+
self._singular_vertices_valid = 0
|
|
256
|
+
|
|
257
|
+
# Populate the normals
|
|
258
|
+
#print('populating face normals')
|
|
259
|
+
self._face_normals_valid = 0
|
|
260
|
+
self.face_normals
|
|
261
|
+
#print('populating vertex normals')
|
|
262
|
+
self._vertex_normals_valid = 0
|
|
263
|
+
self.vertex_normals
|
|
264
|
+
#print('done populating normals')
|
|
265
|
+
|
|
266
|
+
# Properties we can visualize
|
|
267
|
+
self.vertex_properties = ['x', 'y', 'z', 'component', 'boundary', 'singular', 'curvature_mean', 'curvature_gaussian']
|
|
268
|
+
self.vertex_vector_properties = ['vertex_normals']
|
|
269
|
+
|
|
270
|
+
self.extra_vertex_data = {}
|
|
271
|
+
|
|
272
|
+
self.fix_boundary = 1 # Hold boundary edges in place
|
|
273
|
+
|
|
274
|
+
# Is the mesh manifold?
|
|
275
|
+
self._manifold = 0
|
|
276
|
+
|
|
277
|
+
# Curvatures
|
|
278
|
+
self._H = None
|
|
279
|
+
self._K = None
|
|
280
|
+
self.smooth_curvature = 0
|
|
281
|
+
|
|
282
|
+
self._components_valid = 0
|
|
283
|
+
|
|
284
|
+
self.mdh = None
|
|
285
|
+
|
|
286
|
+
# Set fix_boundary, etc.
|
|
287
|
+
for key, value in kwargs.items():
|
|
288
|
+
setattr(self, key, value)
|
|
289
|
+
|
|
290
|
+
def __getitem__(self, k):
|
|
291
|
+
# this defers evaluation of the properties until we actually access them,
|
|
292
|
+
# as opposed to the mappings which stored the values on class creation.
|
|
293
|
+
try:
|
|
294
|
+
res = getattr(self, k)
|
|
295
|
+
except AttributeError:
|
|
296
|
+
try:
|
|
297
|
+
res = self.extra_vertex_data[k]
|
|
298
|
+
except KeyError:
|
|
299
|
+
raise KeyError('Key %s not defined' % k)
|
|
300
|
+
|
|
301
|
+
return res
|
|
302
|
+
|
|
303
|
+
def __copy__(self):
|
|
304
|
+
return type(self)(mesh=self)
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def from_stl(cls, filename, **kwargs):
|
|
308
|
+
"""
|
|
309
|
+
Read from an STL file.
|
|
310
|
+
"""
|
|
311
|
+
from PYME.IO.FileUtils import stl
|
|
312
|
+
|
|
313
|
+
# Load an STL from file
|
|
314
|
+
triangles_stl = stl.load_stl_binary(filename)
|
|
315
|
+
|
|
316
|
+
# Call from_np_stl on the file stream
|
|
317
|
+
return cls.from_np_stl(triangles_stl, **kwargs)
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_ply(cls, filename, **kwargs):
|
|
321
|
+
"""
|
|
322
|
+
Read from PLY file.
|
|
323
|
+
"""
|
|
324
|
+
from PYME.IO.FileUtils import ply
|
|
325
|
+
|
|
326
|
+
# Load a PLY from file
|
|
327
|
+
vertices, faces, _ = ply.load_ply(filename)
|
|
328
|
+
|
|
329
|
+
return cls(vertices, faces, **kwargs)
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def from_np_stl(cls, triangles_stl, origin=[0,0,0], **kwargs):
|
|
333
|
+
"""
|
|
334
|
+
Read from an already-loaded STL stream.
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
|
|
340
|
+
triangles_stl : np.ndarray
|
|
341
|
+
the triangles as a numpy array
|
|
342
|
+
origin: 3-tuple (or array)
|
|
343
|
+
coordinates of the mesh origin (nm). Used to ensure that meshes generated from image isosurfaces line up with those generated from different ROIs / points.
|
|
344
|
+
|
|
345
|
+
"""
|
|
346
|
+
vertices_raw = np.vstack((triangles_stl['vertex0'],
|
|
347
|
+
triangles_stl['vertex1'],
|
|
348
|
+
triangles_stl['vertex2']))
|
|
349
|
+
|
|
350
|
+
vertices_raw = vertices_raw + np.array(origin)[None,:]
|
|
351
|
+
vertices, faces_raw = np.unique(vertices_raw,
|
|
352
|
+
return_inverse=True,
|
|
353
|
+
axis=0)
|
|
354
|
+
faces = faces_raw.reshape(faces_raw.shape[0] // 3, 3, order='F')
|
|
355
|
+
|
|
356
|
+
print('Data munged to vertices, faces')
|
|
357
|
+
return cls(vertices, faces, **kwargs)
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def x(self):
|
|
361
|
+
return self.vertices[:,0]
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def y(self):
|
|
365
|
+
return self.vertices[:,1]
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def z(self):
|
|
369
|
+
return self.vertices[:,2]
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def curvature_mean(self):
|
|
373
|
+
if self._H is None:
|
|
374
|
+
self.calculate_curvatures()
|
|
375
|
+
return self._H
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def curvature_gaussian(self):
|
|
379
|
+
if self._K is None:
|
|
380
|
+
self.calculate_curvatures()
|
|
381
|
+
return self._K
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def component(self):
|
|
385
|
+
# if np.all(self._vertices['component'] == -1):
|
|
386
|
+
if not self._components_valid:
|
|
387
|
+
self.find_connected_components()
|
|
388
|
+
self._components_valid = 1
|
|
389
|
+
return self._vertices['component']
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def vertices(self):
|
|
393
|
+
return self._vertices['position']
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def vertex_mask(self):
|
|
397
|
+
"""
|
|
398
|
+
Returns a mask indicating which vertices are valid
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
return self._vertices['halfedge'] != -1
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def faces(self):
|
|
405
|
+
"""
|
|
406
|
+
Returns faces by vertex for rendering and STL applications.
|
|
407
|
+
"""
|
|
408
|
+
if self._faces_by_vertex is None:
|
|
409
|
+
faces = self._faces['halfedge'][self._faces['halfedge'] != -1]
|
|
410
|
+
v0 = self._halfedges['vertex'][self._halfedges['prev'][faces]]
|
|
411
|
+
v1 = self._halfedges['vertex'][faces]
|
|
412
|
+
v2 = self._halfedges['vertex'][self._halfedges['next'][faces]]
|
|
413
|
+
self._faces_by_vertex = np.vstack([v0, v1, v2]).T
|
|
414
|
+
return self._faces_by_vertex
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def boundary(self):
|
|
418
|
+
#FIXME - is this simply a duplicate of the .singular property?
|
|
419
|
+
self._update_vertex_locally_manifold()
|
|
420
|
+
return (self._vertices['locally_manifold'] == 0)
|
|
421
|
+
|
|
422
|
+
@property
|
|
423
|
+
def singular(self):
|
|
424
|
+
self._update_singular_edge_locally_manifold()
|
|
425
|
+
self._update_singular_vertex_locally_manifold()
|
|
426
|
+
return (self._vertices['locally_manifold'] == 0)
|
|
427
|
+
|
|
428
|
+
@property
|
|
429
|
+
def singular_edges(self):
|
|
430
|
+
"""
|
|
431
|
+
Get a list of singular edges. A singular edge is any edge shared by
|
|
432
|
+
more than two faces.
|
|
433
|
+
"""
|
|
434
|
+
self._update_singular_edge_locally_manifold()
|
|
435
|
+
return np.flatnonzero(self._halfedges['locally_manifold'] == 0)
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def singular_vertices(self):
|
|
439
|
+
"""
|
|
440
|
+
Get a list of singular vertices. This includes endpoints of singular
|
|
441
|
+
edges, and isolated singular vertices. Isolated singular vertices
|
|
442
|
+
have more than one 1-neighbor ring.
|
|
443
|
+
"""
|
|
444
|
+
self._update_singular_edge_locally_manifold()
|
|
445
|
+
self._update_singular_vertex_locally_manifold()
|
|
446
|
+
return np.flatnonzero(self._vertices['locally_manifold'] == 0)
|
|
447
|
+
|
|
448
|
+
def _update_all_face_normals(self, recompute=True):
|
|
449
|
+
"""
|
|
450
|
+
Update face normals
|
|
451
|
+
|
|
452
|
+
If called with recompute=False, only recomputes if face normals have been marked as invalid
|
|
453
|
+
"""
|
|
454
|
+
if recompute or (not self._face_normals_valid):
|
|
455
|
+
triangle_mesh_utils.c_update_all_face_normals(self._faces.shape[0], self._halfedges, self._vertices, self._faces)
|
|
456
|
+
self._face_normals_valid = 1
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def face_normals(self):
|
|
460
|
+
"""
|
|
461
|
+
Return the normal of each triangular face.
|
|
462
|
+
"""
|
|
463
|
+
self._update_all_face_normals(recompute=False)
|
|
464
|
+
return self._faces['normal'][self._faces['halfedge'] != -1]
|
|
465
|
+
|
|
466
|
+
@property
|
|
467
|
+
def face_areas(self):
|
|
468
|
+
"""
|
|
469
|
+
Return the area of each triangular face.
|
|
470
|
+
"""
|
|
471
|
+
self._update_all_face_normals(recompute=False)
|
|
472
|
+
return self._faces['area']
|
|
473
|
+
|
|
474
|
+
def _update_all_vertex_neighbours(self, recompute=True):
|
|
475
|
+
"""
|
|
476
|
+
Update the vertex neighbour information (neighbours, normals, edge lengths)
|
|
477
|
+
|
|
478
|
+
If called with recompute=False, only recomputes if vertex normals have been marked as invalid
|
|
479
|
+
"""
|
|
480
|
+
if recompute or (not self._vertex_normals_valid):
|
|
481
|
+
triangle_mesh_utils.c_update_all_vertex_neighbors(self._vertices.shape[0], self._halfedges, self._vertices, self._faces)
|
|
482
|
+
self._vertex_normals_valid = 1
|
|
483
|
+
|
|
484
|
+
@property
|
|
485
|
+
def vertex_neighbors(self):
|
|
486
|
+
"""
|
|
487
|
+
Return the up to NEIGHBORSIZE neighbors of each vertex.
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
self._update_all_vertex_neighbours(recompute=False)
|
|
491
|
+
return self._vertices['neighbors']
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def vertex_normals(self):
|
|
495
|
+
"""
|
|
496
|
+
Return the normal of each vertex.
|
|
497
|
+
"""
|
|
498
|
+
self._update_all_vertex_neighbours(recompute=False)
|
|
499
|
+
return self._vertices['normal']
|
|
500
|
+
|
|
501
|
+
@property
|
|
502
|
+
def valences(self):
|
|
503
|
+
"""
|
|
504
|
+
Return the valence of each vertex.
|
|
505
|
+
"""
|
|
506
|
+
self._update_all_vertex_neighbours(recompute=False)
|
|
507
|
+
return self._vertices['valence']
|
|
508
|
+
|
|
509
|
+
@property
|
|
510
|
+
def manifold(self):
|
|
511
|
+
"""
|
|
512
|
+
Checks if the mesh is manifold: Is every edge is shared by exactly two
|
|
513
|
+
triangles?
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
if not self._manifold:
|
|
517
|
+
vertex_mask = (self._halfedges['vertex'] != -1)
|
|
518
|
+
twin_mask = (self._halfedges['twin'] == -1)
|
|
519
|
+
self._manifold = (np.sum(self.singular) == 0) and (np.sum(vertex_mask & twin_mask) == 0)
|
|
520
|
+
|
|
521
|
+
return self._manifold
|
|
522
|
+
|
|
523
|
+
@property
|
|
524
|
+
def euler_characteristic(self):
|
|
525
|
+
# TODO: Cache on a parameter
|
|
526
|
+
chi = self._calculate_euler_characteristic()
|
|
527
|
+
|
|
528
|
+
return chi
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def genus(self):
|
|
532
|
+
return (2-self.euler_characteristic)/2
|
|
533
|
+
|
|
534
|
+
@property
|
|
535
|
+
def bbox(self):
|
|
536
|
+
"""
|
|
537
|
+
Mesh bounding box. xl, yl, zl, xu, yu, zu
|
|
538
|
+
"""
|
|
539
|
+
cdef float xl, xu, yl, yu, zl, zu
|
|
540
|
+
v = self._vertices['position'][self._vertices['halfedge'] != -1]
|
|
541
|
+
xl, xu = np.min(v[:,0]), np.max(v[:,0])
|
|
542
|
+
yl, yu = np.min(v[:,1]), np.max(v[:,1])
|
|
543
|
+
zl, zu = np.min(v[:,2]), np.max(v[:,2])
|
|
544
|
+
|
|
545
|
+
return xl, yl, zl, xu, yu, zu
|
|
546
|
+
|
|
547
|
+
def keys(self):
|
|
548
|
+
return list(self.vertex_properties) + list(self.extra_vertex_data.keys())
|
|
549
|
+
|
|
550
|
+
def _update_vertex_locally_manifold(self):
|
|
551
|
+
# identify boundary vertices
|
|
552
|
+
self._vertices['locally_manifold'] = 1
|
|
553
|
+
boundary_edges = (self._halfedges['twin'] == -1) & (self._halfedges['vertex'] != -1)
|
|
554
|
+
self._vertices['locally_manifold'][self._halfedges['vertex'][boundary_edges]] = 0
|
|
555
|
+
self._vertices['locally_manifold'][self._halfedges['vertex'][self._halfedges['prev'][boundary_edges]]] = 0
|
|
556
|
+
|
|
557
|
+
self._singular_vertices_valid = 0
|
|
558
|
+
|
|
559
|
+
def _update_singular_vertex_locally_manifold(self, clear=True):
|
|
560
|
+
if not self._singular_vertices_valid:
|
|
561
|
+
|
|
562
|
+
# identify singular vertices
|
|
563
|
+
if clear:
|
|
564
|
+
# optional so this can be chained with _update_vertex_locally_manifold
|
|
565
|
+
self._vertices['locally_manifold'] = 1
|
|
566
|
+
|
|
567
|
+
# vertices connected to singular edges
|
|
568
|
+
non_manifold = self._halfedges['locally_manifold'] == 0
|
|
569
|
+
self._vertices['locally_manifold'][self._halfedges['vertex'][non_manifold]] = 0
|
|
570
|
+
self._vertices['locally_manifold'][self._halfedges['vertex'][self._halfedges['prev'][non_manifold]]] = 0
|
|
571
|
+
|
|
572
|
+
# isolated signular vertices
|
|
573
|
+
valence = np.zeros(self._vertices.shape[0],dtype=int)
|
|
574
|
+
valence[self._vertices['halfedge'] == -1] = -1
|
|
575
|
+
self._compute_raw_vertex_valences(valence)
|
|
576
|
+
# print("valence check")
|
|
577
|
+
# print(valence[valence != self._vertices['valence']])
|
|
578
|
+
# print(self._vertices['valence'][valence != self._vertices['valence']])
|
|
579
|
+
self._vertices['locally_manifold'][valence != self._vertices['valence']] = 0
|
|
580
|
+
|
|
581
|
+
self._singular_vertices_valid = 1
|
|
582
|
+
|
|
583
|
+
# def __update_singular_edge_locally_manifold(self, clear=True):
|
|
584
|
+
# if not self._singular_edges_valid:
|
|
585
|
+
# if clear:
|
|
586
|
+
# # optional so this can be chained with _update_vertex_locally_manifold
|
|
587
|
+
# self._halfedges['locally_manifold'] = 1
|
|
588
|
+
|
|
589
|
+
# # grab all edges
|
|
590
|
+
# edges = np.vstack([self._halfedges['vertex'][self._halfedges['prev']],
|
|
591
|
+
# self._halfedges['vertex']])
|
|
592
|
+
|
|
593
|
+
# # Sort lo->hi
|
|
594
|
+
# sorted_edges = np.sort(edges, axis=0)
|
|
595
|
+
|
|
596
|
+
# # find the number of unique elements
|
|
597
|
+
# _, idxs, counts = np.unique(sorted_edges,axis=1,return_counts=True,return_inverse=True)
|
|
598
|
+
# singular = np.sum(idxs[None,:] == np.flatnonzero(counts > 2)[:,None],axis=0).astype(bool)
|
|
599
|
+
|
|
600
|
+
# self._halfedges['locally_manifold'][singular & (self._halfedges['vertex'] != -1)] = 0
|
|
601
|
+
|
|
602
|
+
# self._singular_edges_valid = 1
|
|
603
|
+
|
|
604
|
+
def _update_singular_edge_locally_manifold(self, clear=True):
|
|
605
|
+
cdef int i
|
|
606
|
+
cdef int v1, v2, _v1, _v2
|
|
607
|
+
cdef int l_v, n_e
|
|
608
|
+
|
|
609
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
610
|
+
cdef int n_vertices = self._vertices.shape[0]
|
|
611
|
+
cdef int n_slots = n_vertices*n_vertices
|
|
612
|
+
|
|
613
|
+
#cdef int * d
|
|
614
|
+
|
|
615
|
+
if not self._singular_edges_valid:
|
|
616
|
+
d = {}
|
|
617
|
+
|
|
618
|
+
if clear:
|
|
619
|
+
# optional so this can be chained with _update_vertex_locally_manifold
|
|
620
|
+
self._halfedges['locally_manifold'] = 1
|
|
621
|
+
|
|
622
|
+
#print('iterating edges')
|
|
623
|
+
# Use a dictionary to keep track of which edges are already assigned twins
|
|
624
|
+
|
|
625
|
+
for i in range(n_halfedges):
|
|
626
|
+
if self._chalfedges[i].prev == -1:
|
|
627
|
+
continue
|
|
628
|
+
_v1 = self._chalfedges[self._chalfedges[i].prev].vertex
|
|
629
|
+
_v2 = self._chalfedges[i].vertex
|
|
630
|
+
|
|
631
|
+
if (_v1 == -1) or (_v2 == -1):
|
|
632
|
+
pass
|
|
633
|
+
else:
|
|
634
|
+
v1 = min(_v1, _v2)
|
|
635
|
+
v2 = max(_v1, _v2)
|
|
636
|
+
|
|
637
|
+
slot_idx = v1*n_vertices + v2
|
|
638
|
+
|
|
639
|
+
n_e = d.get(slot_idx, 0)
|
|
640
|
+
|
|
641
|
+
d[slot_idx] = n_e + 1
|
|
642
|
+
|
|
643
|
+
for i in range(n_halfedges):
|
|
644
|
+
if self._chalfedges[i].prev == -1:
|
|
645
|
+
continue
|
|
646
|
+
_v1 = self._chalfedges[self._chalfedges[i].prev].vertex
|
|
647
|
+
_v2 = self._chalfedges[i].vertex
|
|
648
|
+
|
|
649
|
+
if (_v1 == -1) or (_v2 == -1):
|
|
650
|
+
self._chalfedges[i].locally_manifold = -1 # leave unassigned so it does not get included in _update_singular_vertex_locally_manifold()
|
|
651
|
+
else:
|
|
652
|
+
v1 = min(_v1, _v2)
|
|
653
|
+
v2 = max(_v1, _v2)
|
|
654
|
+
|
|
655
|
+
slot_idx = v1*n_vertices + v2
|
|
656
|
+
|
|
657
|
+
if d[slot_idx] <=2:
|
|
658
|
+
self._chalfedges[i].locally_manifold = 1
|
|
659
|
+
else:
|
|
660
|
+
self._chalfedges[i].locally_manifold = 0
|
|
661
|
+
|
|
662
|
+
#PyMem_Free(d)
|
|
663
|
+
|
|
664
|
+
self._singular_edges_valid = 1
|
|
665
|
+
|
|
666
|
+
cdef _compute_raw_vertex_valences(self, np.ndarray valence):
|
|
667
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
668
|
+
cdef int i
|
|
669
|
+
|
|
670
|
+
# count the valence
|
|
671
|
+
for i in range(n_halfedges):
|
|
672
|
+
if self._chalfedges[i].vertex == -1:
|
|
673
|
+
continue
|
|
674
|
+
valence[self._chalfedges[i].vertex] += 1
|
|
675
|
+
|
|
676
|
+
def _calculate_euler_characteristic(self, component=None):
|
|
677
|
+
if component is not None:
|
|
678
|
+
if not self._components_valid:
|
|
679
|
+
self.find_connected_components()
|
|
680
|
+
self._components_valid = 1
|
|
681
|
+
|
|
682
|
+
faces = self._faces['halfedge'][self._halfedges['component'][self._faces['halfedge']] == component]
|
|
683
|
+
else:
|
|
684
|
+
faces = self._faces['halfedge'][self._faces['halfedge'] != -1]
|
|
685
|
+
|
|
686
|
+
v0 = self._halfedges['vertex'][self._halfedges['prev'][faces]]
|
|
687
|
+
v1 = self._halfedges['vertex'][faces]
|
|
688
|
+
v2 = self._halfedges['vertex'][self._halfedges['next'][faces]]
|
|
689
|
+
faces_by_vertex = np.hstack([v0, v1, v2])
|
|
690
|
+
|
|
691
|
+
F = len(faces)
|
|
692
|
+
V = len(set(faces_by_vertex.ravel()))
|
|
693
|
+
|
|
694
|
+
# grab all edges
|
|
695
|
+
edges = np.vstack([faces_by_vertex, np.hstack([v1,v2,v0])]).T
|
|
696
|
+
|
|
697
|
+
# Sort lo->hi
|
|
698
|
+
sorted_edges = np.sort(edges, axis=1)
|
|
699
|
+
|
|
700
|
+
# find the number of unique elements
|
|
701
|
+
E = len(np.unique(sorted_edges,axis=0))
|
|
702
|
+
|
|
703
|
+
chi = V - E + F
|
|
704
|
+
|
|
705
|
+
return chi
|
|
706
|
+
|
|
707
|
+
cdef _set_chalfedges(self, np.ndarray halfedges):
|
|
708
|
+
self._chalfedges = <halfedge_t *> np.PyArray_DATA(halfedges)
|
|
709
|
+
self._n_edges = np.PyArray_DIM(halfedges, 0)
|
|
710
|
+
|
|
711
|
+
cdef _set_cfaces(self, np.ndarray faces):
|
|
712
|
+
self._cfaces = <face_d *> np.PyArray_DATA(faces)
|
|
713
|
+
self._n_faces = np.PyArray_DIM(faces, 0)
|
|
714
|
+
|
|
715
|
+
cdef _set_cvertices(self, np.ndarray vertices):
|
|
716
|
+
self._cvertices = <vertex_d *> np.PyArray_DATA(vertices)
|
|
717
|
+
self._n_vertices = np.PyArray_DIM(vertices, 0)
|
|
718
|
+
|
|
719
|
+
def _initialize_halfedges(self, vertices, faces):
|
|
720
|
+
"""
|
|
721
|
+
Accepts unordered vertices, indices parameterization of a triangular
|
|
722
|
+
mesh from an STL file and converts to topologically-connected halfedge
|
|
723
|
+
representation.
|
|
724
|
+
|
|
725
|
+
Parameters
|
|
726
|
+
----------
|
|
727
|
+
vertices : np.array
|
|
728
|
+
N x 3 array of Euclidean points.
|
|
729
|
+
faces : np .array
|
|
730
|
+
M x 3 array of vertex indices indicating triangle connectivity.
|
|
731
|
+
Expects STL redundancy.
|
|
732
|
+
"""
|
|
733
|
+
if vertices is not None and faces is not None:
|
|
734
|
+
# Unordered halfedges
|
|
735
|
+
edges = np.vstack([faces[:,[0,1]], faces[:,[1,2]], faces[:,[2,0]]])
|
|
736
|
+
|
|
737
|
+
# Now order them...
|
|
738
|
+
n_faces = len(faces)
|
|
739
|
+
n_edges = len(edges)
|
|
740
|
+
j = np.arange(n_faces)
|
|
741
|
+
|
|
742
|
+
self._halfedges = np.zeros(n_edges, dtype=HALFEDGE_DTYPE)# .view(np.recarray)
|
|
743
|
+
self._set_chalfedges(self._halfedges)
|
|
744
|
+
|
|
745
|
+
self._halfedges[:] = -1 # initialize everything to -1 to start with
|
|
746
|
+
|
|
747
|
+
self._halfedges['vertex'] = edges[:, 1]
|
|
748
|
+
|
|
749
|
+
self._faces = np.zeros(n_faces, dtype=FACE_DTYPE)
|
|
750
|
+
# Flatten to address cython view problem
|
|
751
|
+
_flat_faces = self._faces.view(FACE_DTYPE2)
|
|
752
|
+
# Set up c views to arrays
|
|
753
|
+
self._set_cfaces(_flat_faces)
|
|
754
|
+
self._faces[:] = -1 # initialize everything to -1 to start with
|
|
755
|
+
|
|
756
|
+
# Sort the edges lo->hi so we can arrange them uniquely
|
|
757
|
+
# Convert to list of tuples for use with dictionary
|
|
758
|
+
edges_packed = [tuple(e) for e in np.sort(edges, axis=1)]
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
#print('iterating edges')
|
|
762
|
+
# Use a dictionary to keep track of which edges are already assigned twins
|
|
763
|
+
d = {}
|
|
764
|
+
for i, e in enumerate(edges_packed):
|
|
765
|
+
if e in d:
|
|
766
|
+
idx = d.pop(e)
|
|
767
|
+
if self._halfedges['vertex'][idx] == self._halfedges['vertex'][i]:
|
|
768
|
+
# Don't assign a halfedge to multivalent edges. Note this doesn't
|
|
769
|
+
# account for all multivalent edges, but does prevent double-
|
|
770
|
+
# assignment of twins.
|
|
771
|
+
continue
|
|
772
|
+
|
|
773
|
+
self._halfedges['twin'][idx] = i
|
|
774
|
+
self._halfedges['twin'][i] = idx
|
|
775
|
+
else:
|
|
776
|
+
d[e] = i
|
|
777
|
+
|
|
778
|
+
self._halfedges['face'] = np.hstack([j, j, j])
|
|
779
|
+
self._halfedges['next'] = np.hstack([j+n_faces, j+2*n_faces, j])
|
|
780
|
+
self._halfedges['prev'] = np.hstack([j+2*n_faces, j, j+n_faces])
|
|
781
|
+
self._halfedges['length'] = np.linalg.norm(self._vertices['position'][self._halfedges['vertex']] - self._vertices['position'][self._halfedges['vertex'][self._halfedges['prev']]], axis=1)
|
|
782
|
+
|
|
783
|
+
# Set the vertex halfedges (multiassignment, but should be fine)
|
|
784
|
+
self._vertices['halfedge'][self._halfedges['vertex']] = self._halfedges['next']
|
|
785
|
+
|
|
786
|
+
self._faces['halfedge'] = j # Faces are defined by associated halfedges
|
|
787
|
+
else:
|
|
788
|
+
raise ValueError('Mesh does not contain vertices and faces.')
|
|
789
|
+
|
|
790
|
+
def build_from_verts_faces(self, vertices, faces, clear=False):
|
|
791
|
+
"""
|
|
792
|
+
Construct mesh from vertices and faces.
|
|
793
|
+
"""
|
|
794
|
+
self._vertices = np.zeros(vertices.shape[0], dtype=VERTEX_DTYPE)
|
|
795
|
+
# Flatten to address cython view problem
|
|
796
|
+
_flat_vertices = self._vertices.view(VERTEX_DTYPE2)
|
|
797
|
+
# Set up c views to arrays
|
|
798
|
+
#print self._flat_vertices.shape
|
|
799
|
+
self._set_cvertices(_flat_vertices)
|
|
800
|
+
self._vertices[:] = -1 # initialize everything to -1 to start with
|
|
801
|
+
self._vertices['position'] = vertices
|
|
802
|
+
self._vertex_vacancies = []
|
|
803
|
+
|
|
804
|
+
self._faces = None # Contains a pointer to one halfedge associated with each face
|
|
805
|
+
self._face_vacancies = []
|
|
806
|
+
|
|
807
|
+
# Halfedges
|
|
808
|
+
self._halfedges = None
|
|
809
|
+
self._halfedge_vacancies = []
|
|
810
|
+
|
|
811
|
+
print('initializing halfedges ...')
|
|
812
|
+
print('vertices.shape = %s, faces.shape = %s' % (vertices.shape, faces.shape))
|
|
813
|
+
if vertices.shape[0] >= MAX_VERTEX_COUNT:
|
|
814
|
+
raise RuntimeError('Maximum vertex count is %d, mesh has %d' % (MAX_VERTEX_COUNT, vertices.shape[0]))
|
|
815
|
+
self._initialize_halfedges(vertices, faces)
|
|
816
|
+
print('done initializing halfedges')
|
|
817
|
+
|
|
818
|
+
if clear:
|
|
819
|
+
self._invalidate_cached_properties()
|
|
820
|
+
self._update_all_face_normals()
|
|
821
|
+
self._update_all_vertex_neighbours()
|
|
822
|
+
|
|
823
|
+
def _invalidate_cached_properties(self):
|
|
824
|
+
"""
|
|
825
|
+
Invalidate properties which have been cached and will now need to be recomputed due to changes in mesh
|
|
826
|
+
"""
|
|
827
|
+
self._faces_by_vertex = None
|
|
828
|
+
self._manifold = 0
|
|
829
|
+
# self._singular_edges = None
|
|
830
|
+
# self._singular_vertices = None
|
|
831
|
+
self._singular_edges_valid = 0
|
|
832
|
+
self._singular_vertices_valid = 0
|
|
833
|
+
self._components_valid = 0
|
|
834
|
+
self._face_normals_valid = 0
|
|
835
|
+
self._vertex_normals_valid = 0
|
|
836
|
+
self._H = None
|
|
837
|
+
self._K = None
|
|
838
|
+
#self._E = None
|
|
839
|
+
#self._pE = None
|
|
840
|
+
|
|
841
|
+
@cython.boundscheck(False) # Deactivate bounds checking
|
|
842
|
+
@cython.wraparound(False)
|
|
843
|
+
def _update_face_normals(self, f_idxs):
|
|
844
|
+
"""
|
|
845
|
+
Recompute len(f_idxs) face normals.
|
|
846
|
+
|
|
847
|
+
Parameters
|
|
848
|
+
----------
|
|
849
|
+
f_idxs : list or np.array
|
|
850
|
+
List of face indices to recompute.
|
|
851
|
+
"""
|
|
852
|
+
|
|
853
|
+
if isinstance(f_idxs, list):
|
|
854
|
+
f_idxs = np.int32(f_idxs)
|
|
855
|
+
#triangle_mesh_utils.c_update_face_normals(f_idxs, self._halfedges, self._vertices, self._faces)
|
|
856
|
+
cdef int32_t [:] idxs = f_idxs
|
|
857
|
+
|
|
858
|
+
_update_face_normals(&(idxs[0]), self._chalfedges, <vertex_t *> self._cvertices, <face_t *> self._cfaces, idxs.shape[0])
|
|
859
|
+
|
|
860
|
+
def _update_vertex_neighbors(self, v_idxs):
|
|
861
|
+
"""
|
|
862
|
+
Recalculate len(v_idxs) vertex neighbors/normals.
|
|
863
|
+
|
|
864
|
+
Parameters
|
|
865
|
+
----------
|
|
866
|
+
v_idxs : list or np.array
|
|
867
|
+
List of vertex indicies indicating which vertices to update.
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
if isinstance(v_idxs, list):
|
|
871
|
+
v_idxs = np.int32(v_idxs)
|
|
872
|
+
triangle_mesh_utils.c_update_vertex_neighbors(v_idxs, self._halfedges, self._vertices, self._faces)
|
|
873
|
+
|
|
874
|
+
def _resize(self, vec, axis=0, return_orig=False, skip_entries=False, key=None, new_items=1):
|
|
875
|
+
"""
|
|
876
|
+
Increase the size of an input vector 1.5x, keeping all active data.
|
|
877
|
+
|
|
878
|
+
Parameters
|
|
879
|
+
---------
|
|
880
|
+
vec : np.array
|
|
881
|
+
Vector to expand, -1 stored in unused entries.
|
|
882
|
+
axis : int
|
|
883
|
+
Axis (0 or 1) along which to resize vector (only works for 2D
|
|
884
|
+
arrays).
|
|
885
|
+
return_orig : bool
|
|
886
|
+
Return indices of non-negative-one entries in the array prior
|
|
887
|
+
to resize.
|
|
888
|
+
key : string
|
|
889
|
+
Optional array key on which to check for -1 value.
|
|
890
|
+
"""
|
|
891
|
+
|
|
892
|
+
if (axis > 1) or (len(vec.shape) > 2):
|
|
893
|
+
raise NotImplementedError('This function only works for 2D arrays along axes 0 and 1.')
|
|
894
|
+
|
|
895
|
+
# Increase the size 1.5x along the axis.
|
|
896
|
+
dt = vec.dtype
|
|
897
|
+
new_size = np.array(vec.shape)
|
|
898
|
+
new_size[axis] = int(max(1.5*new_size[axis], new_size[axis]+ new_items + 1) + 0.5)
|
|
899
|
+
new_size = tuple(new_size)
|
|
900
|
+
|
|
901
|
+
# Allocate memory for new array
|
|
902
|
+
new_vec = np.empty(new_size, dtype=dt)
|
|
903
|
+
|
|
904
|
+
# Assign values to the new array
|
|
905
|
+
new_vec[:] = -1
|
|
906
|
+
|
|
907
|
+
if skip_entries:
|
|
908
|
+
# Check for invalid entries
|
|
909
|
+
if key:
|
|
910
|
+
copy_mask = (vec[key] != 1)
|
|
911
|
+
else:
|
|
912
|
+
copy_mask = (vec != -1)
|
|
913
|
+
|
|
914
|
+
if len(copy_mask.shape) > 1:
|
|
915
|
+
copy_mask = np.any(copy_mask, axis=(1-axis))
|
|
916
|
+
copy_size = np.flatnonzero(copy_mask).size
|
|
917
|
+
else:
|
|
918
|
+
copy_size = np.flatnonzero(copy_mask).size
|
|
919
|
+
|
|
920
|
+
if axis:
|
|
921
|
+
new_vec[:,:copy_size] = vec[:, copy_mask]
|
|
922
|
+
else:
|
|
923
|
+
new_vec[:copy_size] = vec[copy_mask]
|
|
924
|
+
|
|
925
|
+
# Return positions of non-negative-1 entries prior to re-ordering
|
|
926
|
+
if return_orig:
|
|
927
|
+
_orig = np.where(copy_mask)[0]
|
|
928
|
+
# Return empty list if original values are all at the same place
|
|
929
|
+
if np.all(_orig == np.arange(len(_orig))):
|
|
930
|
+
_orig = []
|
|
931
|
+
return new_vec, _orig
|
|
932
|
+
|
|
933
|
+
return new_vec
|
|
934
|
+
else:
|
|
935
|
+
# We're not skipping entries
|
|
936
|
+
if axis:
|
|
937
|
+
copy_size = vec.shape[1]
|
|
938
|
+
new_vec[:,:copy_size] = vec
|
|
939
|
+
else:
|
|
940
|
+
copy_size = vec.shape[0]
|
|
941
|
+
new_vec[:copy_size] = vec
|
|
942
|
+
|
|
943
|
+
if return_orig:
|
|
944
|
+
return new_vec, []
|
|
945
|
+
|
|
946
|
+
# Update array
|
|
947
|
+
return new_vec
|
|
948
|
+
|
|
949
|
+
def calculate_curvatures(self):
|
|
950
|
+
# TODO - compare / consolidate with ch-shrinkwrap version
|
|
951
|
+
dN = 0.1
|
|
952
|
+
self._H = np.zeros(self._vertices.shape[0])
|
|
953
|
+
self._K = np.zeros(self._vertices.shape[0])
|
|
954
|
+
I = np.eye(3)
|
|
955
|
+
areas = np.zeros(self._vertices.shape[0])
|
|
956
|
+
for iv in range(self._vertices.shape[0]):
|
|
957
|
+
if self._vertices['halfedge'][iv] == -1:
|
|
958
|
+
continue
|
|
959
|
+
|
|
960
|
+
# Vertex and its normal
|
|
961
|
+
vi = self._vertices['position'][iv,:]
|
|
962
|
+
Nvi = self._vertices['normal'][iv,:]
|
|
963
|
+
|
|
964
|
+
p = I - Nvi[:,None]*Nvi[None,:] # np.outer(Nvi, Nvi)
|
|
965
|
+
|
|
966
|
+
# vertex nearest neighbors
|
|
967
|
+
neighbors = self._vertices['neighbors'][iv]
|
|
968
|
+
neighbor_mask = (neighbors != -1)
|
|
969
|
+
neighbor_vertices = self._halfedges['vertex'][neighbors]
|
|
970
|
+
vjs = self._vertices['position'][neighbor_vertices[neighbor_mask]]
|
|
971
|
+
|
|
972
|
+
# Neighbor vectors & displaced neighbor tangents
|
|
973
|
+
dvs = vjs - vi[None,:]
|
|
974
|
+
|
|
975
|
+
# radial weighting
|
|
976
|
+
r_sum = np.sum(1./np.sqrt((dvs*dvs).sum(1)))
|
|
977
|
+
|
|
978
|
+
# Norms
|
|
979
|
+
dvs_norm = np.sqrt((dvs*dvs).sum(1))
|
|
980
|
+
|
|
981
|
+
# Hats
|
|
982
|
+
dvs_hat = dvs/dvs_norm[:,None]
|
|
983
|
+
|
|
984
|
+
# Tangents
|
|
985
|
+
T_thetas = np.dot(p,-dvs.T).T
|
|
986
|
+
Tijs = T_thetas/np.sqrt((T_thetas*T_thetas).sum(1)[:,None])
|
|
987
|
+
Tijs[np.sum(T_thetas,axis=1) == 0, :] = 0
|
|
988
|
+
|
|
989
|
+
# Edge normals subtracted from vertex normals
|
|
990
|
+
Ni_diffs = np.sqrt(2. - 2.*np.sqrt(1.-((Nvi[None,:]*dvs_hat).sum(1))**2))
|
|
991
|
+
|
|
992
|
+
k = 2.*np.sign((Nvi[None,:]*(-dvs)).sum(1))*Ni_diffs/dvs_norm
|
|
993
|
+
w = (1./dvs_norm)/r_sum
|
|
994
|
+
|
|
995
|
+
Mvi = (w[None,:,None]*k[None,:,None]*Tijs.T[:,:,None]*Tijs[None,:,:]).sum(axis=1)
|
|
996
|
+
|
|
997
|
+
# Solve the eigenproblem in closed form
|
|
998
|
+
m00 = Mvi[0,0]
|
|
999
|
+
m01 = Mvi[0,1]
|
|
1000
|
+
m02 = Mvi[0,2]
|
|
1001
|
+
m11 = Mvi[1,1]
|
|
1002
|
+
m12 = Mvi[1,2]
|
|
1003
|
+
m22 = Mvi[2,2]
|
|
1004
|
+
|
|
1005
|
+
# Here we use the fact that Mvi is symnmetric and we know
|
|
1006
|
+
# one of the eigenvalues must be 0
|
|
1007
|
+
p = -m00*m11 - m00*m22 + m01*m01 + m02*m02 - m11*m22 + m12*m12
|
|
1008
|
+
q = m00 + m11 + m22
|
|
1009
|
+
r = np.sqrt(4*p + q*q)
|
|
1010
|
+
|
|
1011
|
+
# Eigenvalues
|
|
1012
|
+
l1 = 0.5*(q-r)
|
|
1013
|
+
l2 = 0.5*(q+r)
|
|
1014
|
+
|
|
1015
|
+
k_1 = 3.*l1 - l2 #e[0] - e[1]
|
|
1016
|
+
k_2 = 3.*l2 - l1 #e[1] - e[0]
|
|
1017
|
+
self._H[iv] = 0.5*(k_1 + k_2)
|
|
1018
|
+
self._K[iv] = k_1*k_2
|
|
1019
|
+
|
|
1020
|
+
if self.smooth_curvature:
|
|
1021
|
+
self._H = self.smooth_per_vertex_data(self._H)
|
|
1022
|
+
self._K = self.smooth_per_vertex_data(self._K)
|
|
1023
|
+
|
|
1024
|
+
def smooth_per_vertex_data(self, data):
|
|
1025
|
+
# replace a vertex value with the average of that value and it's neighbours. TODO - add some form of weighting
|
|
1026
|
+
out = np.zeros_like(data)
|
|
1027
|
+
for iv in range(self._vertices.shape[0]):
|
|
1028
|
+
# Vertex position
|
|
1029
|
+
#vi = self._vertices['position'][iv,:]
|
|
1030
|
+
|
|
1031
|
+
# vertex nearest neighbors
|
|
1032
|
+
neighbors = self._vertices['neighbors'][iv]
|
|
1033
|
+
neighbor_vertices = self._halfedges['vertex'][neighbors[neighbors != -1]]
|
|
1034
|
+
|
|
1035
|
+
#vjs = self._vertices['position'][neighbor_vertices]
|
|
1036
|
+
|
|
1037
|
+
#TODO add weightings ...
|
|
1038
|
+
out[iv] = (data[iv] + data[neighbor_vertices].sum())/float(1 + len(neighbor_vertices))
|
|
1039
|
+
|
|
1040
|
+
return out
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
cdef bint _check_neighbour_twins(self, int vertex_id):
|
|
1044
|
+
cdef int i
|
|
1045
|
+
cdef int32_t *neighbours = &(self._cvertices[vertex_id].neighbor0)
|
|
1046
|
+
for i in range(NEIGHBORSIZE):
|
|
1047
|
+
nn = neighbours[i]
|
|
1048
|
+
if (nn != -1) and (self._chalfedges[nn].twin == -1):
|
|
1049
|
+
return 0
|
|
1050
|
+
|
|
1051
|
+
return 1
|
|
1052
|
+
|
|
1053
|
+
cdef int _face_delete(self, int32_t _edge):
|
|
1054
|
+
"""
|
|
1055
|
+
Delete a face defined by the halfedge _edge.
|
|
1056
|
+
|
|
1057
|
+
Note that this does not account for adjusting halfedges, twins.
|
|
1058
|
+
|
|
1059
|
+
Parameters
|
|
1060
|
+
----------
|
|
1061
|
+
edge : int
|
|
1062
|
+
One self._halfedge index of the edge defining a face to delete.
|
|
1063
|
+
"""
|
|
1064
|
+
cdef halfedge_t *curr_edge
|
|
1065
|
+
|
|
1066
|
+
if _edge == -1:
|
|
1067
|
+
return 0
|
|
1068
|
+
|
|
1069
|
+
curr_edge = &self._chalfedges[_edge]
|
|
1070
|
+
if curr_edge.vertex == -1:
|
|
1071
|
+
return 0
|
|
1072
|
+
|
|
1073
|
+
self._cfaces[curr_edge.face].halfedge = -1
|
|
1074
|
+
self._cfaces[curr_edge.face].normal0 = -1
|
|
1075
|
+
self._cfaces[curr_edge.face].normal1 = -1
|
|
1076
|
+
self._cfaces[curr_edge.face].normal2 = -1
|
|
1077
|
+
self._cfaces[curr_edge.face].area = -1
|
|
1078
|
+
self._cfaces[curr_edge.face].component = -1
|
|
1079
|
+
|
|
1080
|
+
self._face_vacancies.append(curr_edge.face)
|
|
1081
|
+
|
|
1082
|
+
self._edge_delete(curr_edge.next)
|
|
1083
|
+
self._edge_delete(curr_edge.prev)
|
|
1084
|
+
self._edge_delete(_edge)
|
|
1085
|
+
|
|
1086
|
+
self._invalidate_cached_properties()
|
|
1087
|
+
|
|
1088
|
+
return 1
|
|
1089
|
+
|
|
1090
|
+
cdef int _edge_delete(self, int32_t _edge):
|
|
1091
|
+
"""
|
|
1092
|
+
Delete edge _edge in place from self._halfedges.
|
|
1093
|
+
|
|
1094
|
+
Note that this does not account for adjusting halfedges, twins.
|
|
1095
|
+
|
|
1096
|
+
Parameters
|
|
1097
|
+
----------
|
|
1098
|
+
edge : int
|
|
1099
|
+
One self._halfedge index of the edge to delete.
|
|
1100
|
+
"""
|
|
1101
|
+
if (_edge == -1):
|
|
1102
|
+
return 0
|
|
1103
|
+
|
|
1104
|
+
self._chalfedges[_edge].vertex = -1
|
|
1105
|
+
self._chalfedges[_edge].face = -1
|
|
1106
|
+
self._chalfedges[_edge].twin = -1
|
|
1107
|
+
self._chalfedges[_edge].next = -1
|
|
1108
|
+
self._chalfedges[_edge].prev = -1
|
|
1109
|
+
self._chalfedges[_edge].length = -1
|
|
1110
|
+
self._chalfedges[_edge].component = -1
|
|
1111
|
+
self._chalfedges[_edge].locally_manifold = -1
|
|
1112
|
+
|
|
1113
|
+
self._halfedge_vacancies.append(_edge)
|
|
1114
|
+
|
|
1115
|
+
self._invalidate_cached_properties()
|
|
1116
|
+
|
|
1117
|
+
return 1
|
|
1118
|
+
|
|
1119
|
+
cdef int _vertex_delete(self, int32_t v_idx):
|
|
1120
|
+
if v_idx == -1:
|
|
1121
|
+
return 0
|
|
1122
|
+
|
|
1123
|
+
self._cvertices[v_idx].position0 = -1
|
|
1124
|
+
self._cvertices[v_idx].position1 = -1
|
|
1125
|
+
self._cvertices[v_idx].position2 = -1
|
|
1126
|
+
self._cvertices[v_idx].normal0 = -1
|
|
1127
|
+
self._cvertices[v_idx].normal1 = -1
|
|
1128
|
+
self._cvertices[v_idx].normal2 = -1
|
|
1129
|
+
self._cvertices[v_idx].halfedge = -1
|
|
1130
|
+
self._cvertices[v_idx].valence = -1
|
|
1131
|
+
self._cvertices[v_idx].neighbor0 = -1
|
|
1132
|
+
self._cvertices[v_idx].neighbor1 = -1
|
|
1133
|
+
self._cvertices[v_idx].neighbor2 = -1
|
|
1134
|
+
self._cvertices[v_idx].neighbor3 = -1
|
|
1135
|
+
self._cvertices[v_idx].neighbor4 = -1
|
|
1136
|
+
self._cvertices[v_idx].neighbor5 = -1
|
|
1137
|
+
self._cvertices[v_idx].neighbor6 = -1
|
|
1138
|
+
self._cvertices[v_idx].neighbor7 = -1
|
|
1139
|
+
self._cvertices[v_idx].neighbor8 = -1
|
|
1140
|
+
self._cvertices[v_idx].neighbor9 = -1
|
|
1141
|
+
self._cvertices[v_idx].neighbor10 = -1
|
|
1142
|
+
self._cvertices[v_idx].neighbor11 = -1
|
|
1143
|
+
self._cvertices[v_idx].neighbor12 = -1
|
|
1144
|
+
self._cvertices[v_idx].neighbor13 = -1
|
|
1145
|
+
self._cvertices[v_idx].neighbor14 = -1
|
|
1146
|
+
self._cvertices[v_idx].neighbor15 = -1
|
|
1147
|
+
self._cvertices[v_idx].neighbor16 = -1
|
|
1148
|
+
self._cvertices[v_idx].neighbor17 = -1
|
|
1149
|
+
self._cvertices[v_idx].neighbor18 = -1
|
|
1150
|
+
self._cvertices[v_idx].neighbor19 = -1
|
|
1151
|
+
self._cvertices[v_idx].component = -1
|
|
1152
|
+
self._cvertices[v_idx].locally_manifold = -1
|
|
1153
|
+
|
|
1154
|
+
self._vertex_vacancies.append(v_idx)
|
|
1155
|
+
|
|
1156
|
+
self._invalidate_cached_properties()
|
|
1157
|
+
|
|
1158
|
+
return 1
|
|
1159
|
+
|
|
1160
|
+
def _get_insertion_slots(self, int n_slots, el_arr, el_vacancies, key_idx, compact=False):
|
|
1161
|
+
""" get an empty slot to insert into, resizing if needed"""
|
|
1162
|
+
|
|
1163
|
+
if len(el_vacancies) <= n_slots:
|
|
1164
|
+
# not enough vacant slots, resize
|
|
1165
|
+
key = __insertion_keys[key_idx]
|
|
1166
|
+
el_arr = self._resize(el_arr, skip_entries=compact, key=key, new_items=n_slots)
|
|
1167
|
+
#TODO - invalidate neighbours, vertex_halfedges etc ???
|
|
1168
|
+
|
|
1169
|
+
# NOTE: If we search by a different key next time, el_vacancies will
|
|
1170
|
+
# still contain the vacant positions when searching on the previous key.
|
|
1171
|
+
el_vacancies = self._update_vacancies(el_arr, key)
|
|
1172
|
+
|
|
1173
|
+
idx = el_vacancies[-n_slots:]
|
|
1174
|
+
el_vacancies = el_vacancies[:-n_slots]
|
|
1175
|
+
|
|
1176
|
+
if idx == -1:
|
|
1177
|
+
raise ValueError('Index cannot be -1.')
|
|
1178
|
+
|
|
1179
|
+
return idx, el_arr, el_vacancies
|
|
1180
|
+
|
|
1181
|
+
def _update_vacancies(self, el_arr, key):
|
|
1182
|
+
if len(el_arr[key].shape) > 1:
|
|
1183
|
+
# NOTE: If we search on self._vertices['position'] and one position is [-1,-1,-1],
|
|
1184
|
+
# which is not unreasonable, this will replace that vertex. Hence we provide the
|
|
1185
|
+
# option to search and insert on different keys.
|
|
1186
|
+
el_vacancies = [int(x) for x in np.argwhere(np.all(el_arr[key] == -1, axis=1))]
|
|
1187
|
+
else:
|
|
1188
|
+
# el_vacancies = [int(x) for x in np.flatnonzero(el_arr[key] == -1)]
|
|
1189
|
+
el_vacancies = np.flatnonzero(el_arr[key] == -1).tolist()
|
|
1190
|
+
|
|
1191
|
+
return el_vacancies
|
|
1192
|
+
|
|
1193
|
+
def _update_all_vacancies(self):
|
|
1194
|
+
self._halfedge_vacancies = self._update_vacancies(self._halfedges, __insertion_keys[INSERTION_KEY_VERTEX])
|
|
1195
|
+
self._vertex_vacancies = self._update_vacancies(self._vertices, __insertion_keys[INSERTION_KEY_HALFEDGE])
|
|
1196
|
+
self._face_vacancies = self._update_vacancies(self._faces, __insertion_keys[INSERTION_KEY_HALFEDGE])
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
cdef _populate_edge(self, int idx, int vertex, int prev=-1, int next=-1, int face=-1, int twin=-1, int locally_manifold=1):
|
|
1201
|
+
# TODO - make a macro??
|
|
1202
|
+
self._chalfedges[idx].vertex = vertex
|
|
1203
|
+
self._chalfedges[idx].prev = prev
|
|
1204
|
+
self._chalfedges[idx].next = next
|
|
1205
|
+
self._chalfedges[idx].face = face
|
|
1206
|
+
self._chalfedges[idx].twin = twin
|
|
1207
|
+
self._chalfedges[idx].locally_manifold = locally_manifold
|
|
1208
|
+
|
|
1209
|
+
def new_edges(self, int n_edges):
|
|
1210
|
+
idx, self._halfedges, self._halfedge_vacancies = self._get_insertion_slots(n_edges, self._halfedges, self._halfedge_vacancies, key_idx=INSERTION_KEY_VERTEX)
|
|
1211
|
+
self._set_chalfedges(self._halfedges)
|
|
1212
|
+
|
|
1213
|
+
return np.array(idx, np.int32)
|
|
1214
|
+
|
|
1215
|
+
def new_vertices(self, int n_vertices):
|
|
1216
|
+
idx, self._vertices, self._vertex_vacancies = self._get_insertion_slots(n_vertices, self._vertices, self._vertex_vacancies, key_idx=INSERTION_KEY_HALFEDGE)
|
|
1217
|
+
self._set_cvertices(self._vertices)
|
|
1218
|
+
|
|
1219
|
+
self._invalidate_cached_properties()
|
|
1220
|
+
|
|
1221
|
+
return np.array(idx, np.int32)
|
|
1222
|
+
|
|
1223
|
+
def new_faces(self, int n_faces):
|
|
1224
|
+
idx, self._faces, self._face_vacancies = self._get_insertion_slots(n_faces, self._faces, self._face_vacancies, key_idx=INSERTION_KEY_HALFEDGE)
|
|
1225
|
+
self._set_cfaces(self._faces)
|
|
1226
|
+
|
|
1227
|
+
return np.array(idx, np.int32)
|
|
1228
|
+
|
|
1229
|
+
cpdef int edge_flip(self, int32_t _curr, bint live_update=1):
|
|
1230
|
+
cdef int ret
|
|
1231
|
+
ret = remesh_edge_flip(self._chalfedges, <vertex_t *>&self._cvertices[0], <face_t *> &self._cfaces[0], _curr, self._n_edges, live_update)
|
|
1232
|
+
self._invalidate_cached_properties()
|
|
1233
|
+
return ret
|
|
1234
|
+
|
|
1235
|
+
def split_edges(self, float split_threshold):
|
|
1236
|
+
cdef int split_count = 0
|
|
1237
|
+
cdef int i
|
|
1238
|
+
cdef int e
|
|
1239
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
1240
|
+
cdef float *n1
|
|
1241
|
+
cdef float *n2
|
|
1242
|
+
cdef float nd
|
|
1243
|
+
cdef int n_edge_idx, n_face_idx, n_vertex_idx
|
|
1244
|
+
#self.face_normals
|
|
1245
|
+
#self.vertex_normals
|
|
1246
|
+
|
|
1247
|
+
cdef int* edges_to_split = <int*>PyMem_Malloc(n_halfedges*sizeof(int))
|
|
1248
|
+
if not edges_to_split:
|
|
1249
|
+
raise MemoryError()
|
|
1250
|
+
cdef int* twin_split = <int*>PyMem_Malloc(n_halfedges*sizeof(int))
|
|
1251
|
+
if not twin_split:
|
|
1252
|
+
raise MemoryError()
|
|
1253
|
+
|
|
1254
|
+
for i in range(n_halfedges):
|
|
1255
|
+
twin_split[i] = 0
|
|
1256
|
+
|
|
1257
|
+
for i in range(n_halfedges):
|
|
1258
|
+
if (not twin_split[i]) and (self._chalfedges[i].vertex != -1): # and (self._chalfedges[i].length > split_threshold):
|
|
1259
|
+
# n1 = &self._cvertices[self._chalfedges[i].vertex].normal0
|
|
1260
|
+
# n2 = &self._cvertices[self._chalfedges[self._chalfedges[i].prev].vertex].normal0
|
|
1261
|
+
# nd = n1[0]*n2[0] + n1[1]*n2[1] + n1[2]*n2[2]
|
|
1262
|
+
nd = 1.0
|
|
1263
|
+
if (self._chalfedges[i].length > (nd*split_threshold)):
|
|
1264
|
+
if self._chalfedges[i].twin != -1:
|
|
1265
|
+
twin_split[self._chalfedges[i].twin] = 1
|
|
1266
|
+
edges_to_split[split_count] = i
|
|
1267
|
+
split_count += 1
|
|
1268
|
+
|
|
1269
|
+
n_edges = self.new_edges(int(split_count*6))
|
|
1270
|
+
n_edge_idx = 0
|
|
1271
|
+
n_faces = self.new_faces(int(split_count*2))
|
|
1272
|
+
n_face_idx = 0
|
|
1273
|
+
n_vertices = self.new_vertices(int(split_count))
|
|
1274
|
+
n_vertex_idx = 0
|
|
1275
|
+
#print(self._halfedges[n_edges])
|
|
1276
|
+
|
|
1277
|
+
for i in range(split_count):
|
|
1278
|
+
e = edges_to_split[i]
|
|
1279
|
+
#print(i, e, n_edge_idx, n_edges)
|
|
1280
|
+
# self.edge_split_2(e,
|
|
1281
|
+
# <int32_t *> np.PyArray_DATA(n_edges),
|
|
1282
|
+
# <int32_t *> np.PyArray_DATA(n_vertices),
|
|
1283
|
+
# <int32_t *> np.PyArray_DATA(n_faces),
|
|
1284
|
+
# n_edge_idx, n_vertex_idx, n_face_idx)
|
|
1285
|
+
|
|
1286
|
+
remesh_split_edge(self._chalfedges, n_halfedges, <vertex_t *> self._cvertices, <face_t *>self._cfaces, int(e),
|
|
1287
|
+
<int32_t *> np.PyArray_DATA(n_edges),
|
|
1288
|
+
<int32_t *> np.PyArray_DATA(n_vertices),
|
|
1289
|
+
<int32_t *> np.PyArray_DATA(n_faces),
|
|
1290
|
+
n_edge_idx, n_vertex_idx, n_face_idx, 1, 0)
|
|
1291
|
+
#self.edge_split(e)
|
|
1292
|
+
n_edge_idx += 6
|
|
1293
|
+
n_face_idx += 2
|
|
1294
|
+
n_vertex_idx += 1
|
|
1295
|
+
|
|
1296
|
+
PyMem_Free(edges_to_split)
|
|
1297
|
+
PyMem_Free(twin_split)
|
|
1298
|
+
|
|
1299
|
+
#print('Split count: %d' % (split_count))
|
|
1300
|
+
return split_count
|
|
1301
|
+
|
|
1302
|
+
def collapse_edges(self, float collapse_threshold):
|
|
1303
|
+
cdef int collapse_count = 0
|
|
1304
|
+
cdef int collapse_fails = 0
|
|
1305
|
+
cdef int i
|
|
1306
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
1307
|
+
cdef float *n1
|
|
1308
|
+
cdef float *n2
|
|
1309
|
+
cdef float nd
|
|
1310
|
+
|
|
1311
|
+
#find which vertices are locally manifold
|
|
1312
|
+
self._update_vertex_locally_manifold()
|
|
1313
|
+
|
|
1314
|
+
for i in range(n_halfedges):
|
|
1315
|
+
if (self._chalfedges[i].vertex != -1) and (self._chalfedges[i].length < collapse_threshold):
|
|
1316
|
+
n1 = &self._cvertices[self._chalfedges[i].vertex].normal0
|
|
1317
|
+
n2 = &self._cvertices[self._chalfedges[self._chalfedges[i].prev].vertex].normal0
|
|
1318
|
+
# dot product of the normals
|
|
1319
|
+
nd = n1[0]*n2[0] + n1[1]*n2[1] + n1[2]*n2[2]
|
|
1320
|
+
if (self._chalfedges[i].length < nd*nd*collapse_threshold):
|
|
1321
|
+
#collapse_ret = self.edge_collapse(i)
|
|
1322
|
+
collapse_ret = remesh_edge_collapse(self._chalfedges, self._n_edges, <vertex_t *> self._cvertices, <face_t*> self._cfaces, i, 1)
|
|
1323
|
+
collapse_count += collapse_ret
|
|
1324
|
+
collapse_fails += (1-collapse_ret)
|
|
1325
|
+
|
|
1326
|
+
self._update_all_vacancies() #we might have deleted edges, vertices, etc ....
|
|
1327
|
+
self._invalidate_cached_properties()
|
|
1328
|
+
#print('Collapse count: ' + str(collapse_count) + '[' + str(collapse_fails) +' failed]')
|
|
1329
|
+
|
|
1330
|
+
return collapse_count
|
|
1331
|
+
|
|
1332
|
+
cpdef int regularize(self):
|
|
1333
|
+
"""
|
|
1334
|
+
Adjust vertices so they tend toward valence VALENCE
|
|
1335
|
+
(or BOUNDARY_VALENCE for boundaries).
|
|
1336
|
+
"""
|
|
1337
|
+
|
|
1338
|
+
cdef int i, flip_count, target_valence, r, failed_flip_count
|
|
1339
|
+
cdef halfedge_t *curr_edge
|
|
1340
|
+
cdef halfedge_t *twin_edge
|
|
1341
|
+
cdef int32_t _twin
|
|
1342
|
+
cdef int v1, v2, v3, v4, score_post, score_pre
|
|
1343
|
+
|
|
1344
|
+
flip_count = 0
|
|
1345
|
+
failed_flip_count = 0
|
|
1346
|
+
|
|
1347
|
+
# Do a single pass over all active edges and flip them if the flip minimizes the deviation of vertex valences
|
|
1348
|
+
# for i in np.arange(len(self._halfedges['vertex'])):
|
|
1349
|
+
# if (self._halfedges['vertex'][i] < 0):
|
|
1350
|
+
# continue
|
|
1351
|
+
for i in range(self._halfedges.shape[0]):# np.flatnonzero(self._halfedges['vertex'] != -1).astype(np.int32):
|
|
1352
|
+
curr_edge = &self._chalfedges[i]
|
|
1353
|
+
|
|
1354
|
+
if curr_edge.vertex == -1:
|
|
1355
|
+
continue
|
|
1356
|
+
|
|
1357
|
+
_twin = curr_edge.twin
|
|
1358
|
+
|
|
1359
|
+
v2, v4 = 0, 0
|
|
1360
|
+
target_valence = VALENCE
|
|
1361
|
+
if _twin == -1:
|
|
1362
|
+
# boundary
|
|
1363
|
+
target_valence = BOUNDARY_VALENCE
|
|
1364
|
+
else:
|
|
1365
|
+
twin_edge = &self._chalfedges[_twin]
|
|
1366
|
+
v2 = self._cvertices[twin_edge.vertex].valence - target_valence # pre-flip
|
|
1367
|
+
v4 = self._cvertices[self._chalfedges[twin_edge.next].vertex].valence - target_valence # post-flip
|
|
1368
|
+
|
|
1369
|
+
v1 = self._cvertices[curr_edge.vertex].valence - target_valence # pre-flip
|
|
1370
|
+
v3 = self._cvertices[self._chalfedges[curr_edge.next].vertex].valence - target_valence # post-flip
|
|
1371
|
+
|
|
1372
|
+
# Check valence deviation from VALENCE (or
|
|
1373
|
+
# BOUNDARY_VALENCE for boundaries) pre- and post-flip
|
|
1374
|
+
#score_pre = abs([v1,v2,v3,v4]).sum()
|
|
1375
|
+
#score_post = np.abs([v1-1,v2-1,v3+1,v4+1]).sum()
|
|
1376
|
+
|
|
1377
|
+
score_pre = abs(v1) + abs(v2) + abs(v3) + abs(v4)
|
|
1378
|
+
score_post = abs(v1-1) + abs(v2-1) + abs(v3+1) + abs(v4+1)
|
|
1379
|
+
|
|
1380
|
+
if score_post < score_pre:
|
|
1381
|
+
# Flip minimizes deviation of vertex valences from VALENCE (or
|
|
1382
|
+
# BOUNDARY_VALENCE for boundaries)
|
|
1383
|
+
r = self.edge_flip(i)
|
|
1384
|
+
flip_count += r
|
|
1385
|
+
failed_flip_count += (1-r)
|
|
1386
|
+
|
|
1387
|
+
#print('Flip count: %d [%d failed]' % (flip_count, failed_flip_count))
|
|
1388
|
+
return flip_count
|
|
1389
|
+
|
|
1390
|
+
cpdef int relax(self, float l=1, int n=1):
|
|
1391
|
+
return remesh_relax(self._chalfedges, <vertex_t *> self._cvertices, self._n_vertices, <face_t *> self._cfaces, self._n_faces, l, n)
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
def remesh(self, int n=5, float target_edge_length=-1, float l=0.5, int n_relax=10):
|
|
1395
|
+
"""
|
|
1396
|
+
Produce a higher-quality mesh.
|
|
1397
|
+
|
|
1398
|
+
Follows procedure in Botsch and Kobbelt, A Remeshing Approach to
|
|
1399
|
+
Multiresoluton Modeling, Eurographics Symposium on Geometry Processing,
|
|
1400
|
+
2004.
|
|
1401
|
+
|
|
1402
|
+
Parameters
|
|
1403
|
+
----------
|
|
1404
|
+
n : int
|
|
1405
|
+
Number of remeshing iterations to apply.
|
|
1406
|
+
target_edge_length : float
|
|
1407
|
+
Target edge length for all edges in the mesh.
|
|
1408
|
+
l : float
|
|
1409
|
+
Relaxation regularization term, used to avoid oscillations.
|
|
1410
|
+
n_relax : int
|
|
1411
|
+
Number of Lloyd relaxation (relax()) iterations to apply
|
|
1412
|
+
per remeshing iteration.
|
|
1413
|
+
"""
|
|
1414
|
+
cdef int k, i, split_count, collapse_count, collapse_ret, collapse_fails, n_singular
|
|
1415
|
+
#cdef float target_edge_length
|
|
1416
|
+
|
|
1417
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
1418
|
+
cdef int n_vertices = self._vertices.shape[0]
|
|
1419
|
+
|
|
1420
|
+
if (target_edge_length < 0):
|
|
1421
|
+
# Guess edge_length
|
|
1422
|
+
target_edge_length = np.mean(self._halfedges['length'][self._halfedges['length'] != -1])
|
|
1423
|
+
|
|
1424
|
+
cdef float split_threshold = 1.33*target_edge_length
|
|
1425
|
+
cdef float collapse_threshold = 0.8*target_edge_length
|
|
1426
|
+
|
|
1427
|
+
# modify cutoffs so we don't constantly undo ourselves ...
|
|
1428
|
+
# TOD - should we regularize between split and collapse?
|
|
1429
|
+
#cdef float split_threshold = 1.41*target_edge_length
|
|
1430
|
+
#cdef float collapse_threshold = 0.7*target_edge_length
|
|
1431
|
+
|
|
1432
|
+
#self.regularize()
|
|
1433
|
+
|
|
1434
|
+
self._singular_edges_valid = 0
|
|
1435
|
+
n_singular = self.singular_edges.shape[0]
|
|
1436
|
+
#f"# singular: {n_singular}")
|
|
1437
|
+
|
|
1438
|
+
for k in range(n):
|
|
1439
|
+
# 1. Split all edges longer than (4/3)*target_edge_length at their midpoint.
|
|
1440
|
+
# split_count = 0
|
|
1441
|
+
# for i in range(n_halfedges):
|
|
1442
|
+
# if (self._chalfedges[i].vertex != -1) and (self._chalfedges[i].length > split_threshold):
|
|
1443
|
+
# self.edge_split(i)
|
|
1444
|
+
# split_count += 1
|
|
1445
|
+
# print('Split count: %d' % (split_count))
|
|
1446
|
+
|
|
1447
|
+
ct = self.split_edges(split_threshold)
|
|
1448
|
+
#while (ct > 0):
|
|
1449
|
+
# # Keep spliting until we can't do any more splits (note - may be more effective to just run everything a couple more times)
|
|
1450
|
+
# ct = self.split_edges(split_threshold)
|
|
1451
|
+
|
|
1452
|
+
self._singular_edges_valid = 0
|
|
1453
|
+
n_singular = self.singular_edges.shape[0]
|
|
1454
|
+
#print(f"# singular: {n_singular}")
|
|
1455
|
+
|
|
1456
|
+
ct = self.regularize()
|
|
1457
|
+
while (ct > 0):
|
|
1458
|
+
# Keep flipping until we can't do any more splits (note - may be more effective to just run everything a couple more times)
|
|
1459
|
+
ct = self.regularize()
|
|
1460
|
+
|
|
1461
|
+
self._singular_edges_valid = 0
|
|
1462
|
+
n_singular = self.singular_edges.shape[0]
|
|
1463
|
+
#print(f"# singular: {n_singular}")
|
|
1464
|
+
|
|
1465
|
+
# 2. Collapse all edges shorter than (4/5)*target_edge_length to their midpoint.
|
|
1466
|
+
# collapse_count = 0
|
|
1467
|
+
# collapse_fails = 0
|
|
1468
|
+
#
|
|
1469
|
+
# #find which vertices are locally manifold
|
|
1470
|
+
# # TODO - move this to a helper function
|
|
1471
|
+
# self._vertices['locally_manifold'] = 1
|
|
1472
|
+
# self._vertices['locally_manifold'][self._halfedges['vertex'][self._halfedges['twin'] == -1]] = 0
|
|
1473
|
+
#
|
|
1474
|
+
# for i in range(n_halfedges):
|
|
1475
|
+
# if (self._chalfedges[i].vertex != -1) and (self._chalfedges[i].length < collapse_threshold):
|
|
1476
|
+
# collapse_ret = self.edge_collapse(i)
|
|
1477
|
+
# collapse_count += collapse_ret
|
|
1478
|
+
# collapse_fails += (1-collapse_ret)
|
|
1479
|
+
# print('Collapse count: ' + str(collapse_count) + '[' + str(collapse_fails) +' failed]')
|
|
1480
|
+
|
|
1481
|
+
ct = self.collapse_edges(collapse_threshold)
|
|
1482
|
+
|
|
1483
|
+
self._singular_edges_valid = 0
|
|
1484
|
+
n_singular = self.singular_edges.shape[0]
|
|
1485
|
+
#print(f"# singular: {n_singular}")
|
|
1486
|
+
# while (ct > 0):
|
|
1487
|
+
# # Keep collapsing until we can't do any more splits (note - may be more effective to just run everything a couple more times)
|
|
1488
|
+
# ct = self.collapse_edges(collapse_threshold)
|
|
1489
|
+
|
|
1490
|
+
# 3. Flip edges in order to minimize deviation from VALENCE.
|
|
1491
|
+
|
|
1492
|
+
# find which vertices are locally manifold
|
|
1493
|
+
self._update_vertex_locally_manifold()
|
|
1494
|
+
|
|
1495
|
+
ct = self.regularize()
|
|
1496
|
+
_n_flip = 0
|
|
1497
|
+
while (ct > 0) and (_n_flip < 20):
|
|
1498
|
+
# Keep flipping until we can't do any more splits (note - may be more effective to just run everything a couple more times)
|
|
1499
|
+
ct = self.regularize()
|
|
1500
|
+
_n_flip += 1
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
self._singular_edges_valid = 0
|
|
1504
|
+
n_singular = self.singular_edges.shape[0]
|
|
1505
|
+
#print(f"# singular: {n_singular}")
|
|
1506
|
+
|
|
1507
|
+
# 4. Relocate vertices on the surface by tangential smoothing.
|
|
1508
|
+
self.relax(l=l, n=n_relax)
|
|
1509
|
+
|
|
1510
|
+
self._singular_edges_valid = 0
|
|
1511
|
+
n_singular = self.singular_edges.shape[0]
|
|
1512
|
+
#print(f"# singular: {n_singular}")
|
|
1513
|
+
|
|
1514
|
+
# Let's double-check the mesh manifoldness
|
|
1515
|
+
self._manifold = 0
|
|
1516
|
+
self.manifold
|
|
1517
|
+
|
|
1518
|
+
def upsample(self, int n=1):
|
|
1519
|
+
"""
|
|
1520
|
+
Upsample the mesh by a factor of 4 (factor of 2 along each axis in the
|
|
1521
|
+
plane of the mesh) and smooth meshes by Loop's 5/8 and 3/8 rule.
|
|
1522
|
+
|
|
1523
|
+
References:
|
|
1524
|
+
1. C. T. Loop, "Smooth Subdivision surfaces Based on Triangles,"
|
|
1525
|
+
University of Utah, 1987
|
|
1526
|
+
2. http://462cmu.github.io/asst2_meshedit/, task 4
|
|
1527
|
+
|
|
1528
|
+
Parameters
|
|
1529
|
+
----------
|
|
1530
|
+
n : int
|
|
1531
|
+
Number of upsampling operations to perform.
|
|
1532
|
+
"""
|
|
1533
|
+
cdef int n_edge_idx, n_face_idx, n_vertex_idx, k
|
|
1534
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
1535
|
+
|
|
1536
|
+
for k in range(n):
|
|
1537
|
+
# Reset
|
|
1538
|
+
split_edges = {}
|
|
1539
|
+
self._loop_subdivision_new_vertices = []
|
|
1540
|
+
self._loop_subdivision_flip_edges = []
|
|
1541
|
+
new_vertex_idxs = []
|
|
1542
|
+
|
|
1543
|
+
# Mark the current edges as old
|
|
1544
|
+
edges_to_split = np.where(self._halfedges['vertex'] != -1)[0]
|
|
1545
|
+
split_halfedges = self._halfedges[edges_to_split]
|
|
1546
|
+
|
|
1547
|
+
# Compute new vertex positions for old vertices
|
|
1548
|
+
old_vertex_idxs = split_halfedges['vertex']
|
|
1549
|
+
old_vertices = self._vertices[old_vertex_idxs]
|
|
1550
|
+
old_neighbors = old_vertices['neighbors']
|
|
1551
|
+
neg_mask = (old_neighbors != -1)
|
|
1552
|
+
# alpha is a function of vertex order N that satisifies constraints on equation 3.4 of Loop's thesis
|
|
1553
|
+
alpha = np.tanh(LOOP_ALPHA_FACTOR*old_vertices['valence'])
|
|
1554
|
+
old_vertex_positions = (alpha[:,None])*old_vertices['position'] + ((1-alpha)[:,None])*((1./old_vertices['valence'])[:,None])*np.sum(self._vertices['position'][self._halfedges['vertex'][old_neighbors]]*neg_mask[...,None],axis=1)
|
|
1555
|
+
|
|
1556
|
+
# Compute new vertex positions for new vertices
|
|
1557
|
+
p0 = 0.375*(old_vertices['position'] + self._vertices['position'][self._halfedges['vertex'][split_halfedges['twin']]])
|
|
1558
|
+
p1 = 0.125*(self._vertices['position'][self._halfedges['vertex'][split_halfedges['next']]] + self._vertices['position'][self._halfedges['vertex'][self._halfedges['next'][split_halfedges['twin']]]])
|
|
1559
|
+
new_vertex_positions = p0 + p1
|
|
1560
|
+
|
|
1561
|
+
# 1. Split every edge in the mesh
|
|
1562
|
+
split_count = len(edges_to_split)
|
|
1563
|
+
n_edges = self.new_edges(int(split_count*6))
|
|
1564
|
+
n_edge_idx = 0
|
|
1565
|
+
n_faces = self.new_faces(int(split_count*2))
|
|
1566
|
+
n_face_idx = 0
|
|
1567
|
+
n_vertices = self.new_vertices(int(split_count))
|
|
1568
|
+
n_vertex_idx = 0
|
|
1569
|
+
|
|
1570
|
+
for j, i in enumerate(edges_to_split):
|
|
1571
|
+
if i in split_edges:
|
|
1572
|
+
continue
|
|
1573
|
+
|
|
1574
|
+
split_edges[self._halfedges['twin'][i]] = i
|
|
1575
|
+
new_vertex_idxs.append(j)
|
|
1576
|
+
#self.edge_split(i, upsample=True)
|
|
1577
|
+
# self.edge_split_2(i,
|
|
1578
|
+
# <int32_t *> np.PyArray_DATA(n_edges),
|
|
1579
|
+
# <int32_t *> np.PyArray_DATA(n_vertices),
|
|
1580
|
+
# <int32_t *> np.PyArray_DATA(n_faces),
|
|
1581
|
+
# n_edge_idx, n_vertex_idx, n_face_idx, 1,1)
|
|
1582
|
+
remesh_split_edge(self._chalfedges, n_halfedges, <vertex_t *> self._cvertices, <face_t *>self._cfaces, int(i),
|
|
1583
|
+
<int32_t *> np.PyArray_DATA(n_edges),
|
|
1584
|
+
<int32_t *> np.PyArray_DATA(n_vertices),
|
|
1585
|
+
<int32_t *> np.PyArray_DATA(n_faces),
|
|
1586
|
+
n_edge_idx, n_vertex_idx, n_face_idx, 1, 0)
|
|
1587
|
+
|
|
1588
|
+
n_edge_idx += 6
|
|
1589
|
+
n_face_idx += 2
|
|
1590
|
+
n_vertex_idx += 1
|
|
1591
|
+
|
|
1592
|
+
# 2. Flip any new edge that touches an old vertex and a new vertex
|
|
1593
|
+
for e in self._loop_subdivision_flip_edges:
|
|
1594
|
+
if self._halfedges['vertex'][e] in self._loop_subdivision_new_vertices:
|
|
1595
|
+
continue
|
|
1596
|
+
self.edge_flip(e)
|
|
1597
|
+
|
|
1598
|
+
# Get any boundary vertices
|
|
1599
|
+
boundary_vertex_idxs = self._halfedges['vertex'][(self._halfedges['twin'] == -1)]
|
|
1600
|
+
boundary_vertex_positions = self._vertices['position'][boundary_vertex_idxs]
|
|
1601
|
+
|
|
1602
|
+
# Copy new position values into self._vertices['position']
|
|
1603
|
+
self._vertices['position'][old_vertex_idxs] = old_vertex_positions
|
|
1604
|
+
self._vertices['position'][self._loop_subdivision_new_vertices] = new_vertex_positions[new_vertex_idxs]
|
|
1605
|
+
|
|
1606
|
+
# Restore boundary vertex positions
|
|
1607
|
+
self._vertices['position'][boundary_vertex_idxs] = boundary_vertex_positions
|
|
1608
|
+
|
|
1609
|
+
self._manifold = 0
|
|
1610
|
+
|
|
1611
|
+
def downsample(self, n_triangles=None):
|
|
1612
|
+
"""
|
|
1613
|
+
Mesh downsampling via quadratic error metrics.
|
|
1614
|
+
|
|
1615
|
+
Follows procedure in Garland and Heckbert, Surface Simplification Using
|
|
1616
|
+
Quadtratic Error Metrics.
|
|
1617
|
+
|
|
1618
|
+
Parameters
|
|
1619
|
+
----------
|
|
1620
|
+
n_triangles : int
|
|
1621
|
+
Target number of triangles in the downsampled mesh.
|
|
1622
|
+
"""
|
|
1623
|
+
|
|
1624
|
+
import PYME.experimental._treap as treap
|
|
1625
|
+
|
|
1626
|
+
# 1. Compute quadratic matrices for all initial vertices
|
|
1627
|
+
v = self._vertices['position']
|
|
1628
|
+
n = self._vertices['normal']
|
|
1629
|
+
d = (-n*v).sum(1)
|
|
1630
|
+
p = np.vstack([n.T, d]).T # plane
|
|
1631
|
+
vn = self._vertices['neighbors']
|
|
1632
|
+
vn_mask = (vn != -1)
|
|
1633
|
+
pp = p[self._halfedges['vertex'][vn]]*vn_mask[...,None] # neighbor planes
|
|
1634
|
+
Q = (pp[...,None]*pp[:,:,None,:]).sum(1) # per-vertex quadratic
|
|
1635
|
+
|
|
1636
|
+
# 2. Select all valid pairs
|
|
1637
|
+
# For now, operate on edges. In the future we can also operate on any
|
|
1638
|
+
# pair of vertex positions such that || v_1 - v_2 || < t, where v_1,
|
|
1639
|
+
# v_2 \in R^3, and t \in R is a threshold.
|
|
1640
|
+
|
|
1641
|
+
# 3. Compute optimal contraction target and cost of each contraction
|
|
1642
|
+
# NOTE: we do this for all halfedges, empty or not. This saves us from having to search for
|
|
1643
|
+
# unique halfedges later in step 5. Instead, we just ignore -1 values.
|
|
1644
|
+
edges_mask = (self._halfedges['vertex'] != -1)
|
|
1645
|
+
edges = np.vstack([self._halfedges['vertex'], self._halfedges['vertex'][self._halfedges['prev']]]).T
|
|
1646
|
+
# Mask and shift to avoid a copy operation on Q
|
|
1647
|
+
Q_mask = np.ones((4,4))
|
|
1648
|
+
Q_mask[3,:] = 0
|
|
1649
|
+
Q_shift = np.zeros((4,4))
|
|
1650
|
+
Q_shift[3,3] = 1
|
|
1651
|
+
# h is the solution vector for least squares
|
|
1652
|
+
h = np.array([0,0,0,1])
|
|
1653
|
+
# 3a. Compute the optimal contraction target for each valid pair
|
|
1654
|
+
Q_edge = (Q[edges]).sum(1) # Q paired
|
|
1655
|
+
try:
|
|
1656
|
+
vp = (np.linalg.inv(Q_edge*Q_mask[None,...] + Q_shift[None,...])*h).sum(2)
|
|
1657
|
+
except(np.linalg.LinAlgError):
|
|
1658
|
+
vp = 0.5*np.sum(self._vertices['position'][edges],axis=1)
|
|
1659
|
+
vp = np.vstack([vp.T, np.ones(vp.shape[0])]).T
|
|
1660
|
+
|
|
1661
|
+
# 3b. Compute cost of contractions
|
|
1662
|
+
err = ((vp[:,None,:]*Q_edge).sum(2)*vp).sum(1)
|
|
1663
|
+
|
|
1664
|
+
# 4. Place all the pairs in a heap keyed on cost with the minimum cost pair on top
|
|
1665
|
+
cost_heap = treap.Treap()
|
|
1666
|
+
for i in (np.arange(len(edges))[edges_mask]):
|
|
1667
|
+
cost_heap.insert(np.float32(err[i]), np.int32(i))
|
|
1668
|
+
|
|
1669
|
+
# 5. Iteratively remove the pair of least cost from the heap,
|
|
1670
|
+
# contract this pair, and update the costs of all valid
|
|
1671
|
+
# pairs involving the remaining live vertex.
|
|
1672
|
+
n_faces = np.sum(self._faces['halfedge'] != -1)
|
|
1673
|
+
if n_triangles is None:
|
|
1674
|
+
# Assume we want to downsample by a factor of 4
|
|
1675
|
+
n_triangles = int(0.25*n_faces)
|
|
1676
|
+
collapse_tries = int(0.125*(n_faces - n_triangles))
|
|
1677
|
+
while True:
|
|
1678
|
+
if n_faces <= n_triangles:
|
|
1679
|
+
# Sometimes edge_collapse does not collapse the edge
|
|
1680
|
+
# because it would affect the manifoldness of the mesh,
|
|
1681
|
+
# so we need to see how far off we are from the desired
|
|
1682
|
+
# number of triangles n_triangles.
|
|
1683
|
+
tmp = np.sum(self._faces['halfedge'] != -1)
|
|
1684
|
+
if (n_faces == tmp) or (collapse_tries == 0):
|
|
1685
|
+
# We don't want to double check ourselves forever
|
|
1686
|
+
break
|
|
1687
|
+
# Update our estimate and keep refining the mesh
|
|
1688
|
+
n_faces = tmp
|
|
1689
|
+
collapse_tries -= 1
|
|
1690
|
+
|
|
1691
|
+
# Grab the edge with minimum cost
|
|
1692
|
+
_, _edge = cost_heap.pop()
|
|
1693
|
+
|
|
1694
|
+
if _edge == -1:
|
|
1695
|
+
print('Edge was -1???')
|
|
1696
|
+
continue
|
|
1697
|
+
|
|
1698
|
+
# Compute the new quadratic
|
|
1699
|
+
edge = self._halfedges[_edge]
|
|
1700
|
+
# Grab the live and dead vertex
|
|
1701
|
+
_live_vertex = edge['vertex']
|
|
1702
|
+
_dead_vertex = self._halfedges['vertex'][edge['prev']]
|
|
1703
|
+
Q_edge[_edge] = Q[_live_vertex] + Q[_dead_vertex]
|
|
1704
|
+
|
|
1705
|
+
if (_live_vertex == -1) or (_dead_vertex == -1):
|
|
1706
|
+
print('Vertex was -1???')
|
|
1707
|
+
continue
|
|
1708
|
+
|
|
1709
|
+
# Grab all neighboring halfedges
|
|
1710
|
+
nn_live = self._vertices['neighbors'][_live_vertex]
|
|
1711
|
+
nn_live_mask = (nn_live != -1)
|
|
1712
|
+
nn_live_twin = self._halfedges['twin'][nn_live[nn_live_mask]]
|
|
1713
|
+
nn_live_twin_mask = (nn_live_twin != -1)
|
|
1714
|
+
nn_dead = self._vertices['neighbors'][_dead_vertex]
|
|
1715
|
+
nn_dead_mask = (nn_dead != -1)
|
|
1716
|
+
nn_dead_twin = self._halfedges['twin'][nn_dead[nn_dead_mask]]
|
|
1717
|
+
nn_dead_twin_mask = (nn_dead_twin != -1)
|
|
1718
|
+
neighbors = np.hstack([nn_live[nn_live_mask], nn_live_twin[nn_live_twin_mask], nn_dead[nn_dead_mask], nn_dead_twin[nn_dead_twin_mask]])
|
|
1719
|
+
|
|
1720
|
+
# Eliminate edges touching either vertex from the cost heap
|
|
1721
|
+
for neighbor in neighbors:
|
|
1722
|
+
cost_heap.delete(np.int32(neighbor))
|
|
1723
|
+
|
|
1724
|
+
# Collapse the edge
|
|
1725
|
+
#self.edge_collapse(_edge)
|
|
1726
|
+
remesh_edge_collapse(self._chalfedges, self._n_edges, <vertex_t *> self._cvertices, <face_t*> self._cfaces, _edge, 1)
|
|
1727
|
+
|
|
1728
|
+
# Decrement the estimate of the number of triangles in the
|
|
1729
|
+
# mesh accordingly
|
|
1730
|
+
n_faces -= 2
|
|
1731
|
+
|
|
1732
|
+
# Set the quadratic of _live_vertex to the edge quadratic
|
|
1733
|
+
Q[_live_vertex] = Q_edge[_edge]
|
|
1734
|
+
# self._vertices['position'][_live_vertex] = vp[_edge,:3]
|
|
1735
|
+
|
|
1736
|
+
# Grab the new neighbors
|
|
1737
|
+
nn_live = self._vertices['neighbors'][_live_vertex]
|
|
1738
|
+
nn_live_mask = (nn_live != -1)
|
|
1739
|
+
nn_live_twin = self._halfedges['twin'][nn_live[nn_live_mask]]
|
|
1740
|
+
nn_live_twin_mask = (nn_live_twin != -1)
|
|
1741
|
+
neighbors = np.hstack([nn_live[nn_live_mask], nn_live_twin[nn_live_twin_mask]])
|
|
1742
|
+
|
|
1743
|
+
# Update the costs and positions of each of these neighbors and re-insert them into the queue
|
|
1744
|
+
new_edges = np.vstack([self._halfedges['vertex'][neighbors], self._halfedges['vertex'][self._halfedges['prev'][neighbors]]]).T
|
|
1745
|
+
Q_edge[neighbors] = (Q[new_edges]).sum(1)
|
|
1746
|
+
|
|
1747
|
+
try:
|
|
1748
|
+
vp[neighbors] = (np.linalg.inv(Q_edge[neighbors]*Q_mask[None,...] + Q_shift[None,...])*h).sum(2)
|
|
1749
|
+
except(np.linalg.LinAlgError):
|
|
1750
|
+
vp_temp = 0.5*np.sum(self._vertices['position'][new_edges],axis=1)
|
|
1751
|
+
vp[neighbors] = np.vstack([vp_temp.T, np.ones(vp_temp.shape[0])]).T
|
|
1752
|
+
|
|
1753
|
+
err[neighbors] = ((vp[neighbors,None,:]*Q_edge[neighbors]).sum(2)*vp[neighbors]).sum(1)
|
|
1754
|
+
|
|
1755
|
+
for n in neighbors:
|
|
1756
|
+
cost_heap.insert(np.float32(err[n]), np.int32(n))
|
|
1757
|
+
|
|
1758
|
+
self._update_all_vacancies()
|
|
1759
|
+
self._invalidate_cached_properties()
|
|
1760
|
+
|
|
1761
|
+
self._manifold = 0
|
|
1762
|
+
|
|
1763
|
+
def find_connected_components(self):
|
|
1764
|
+
"""
|
|
1765
|
+
Label connected components of the mesh.
|
|
1766
|
+
"""
|
|
1767
|
+
|
|
1768
|
+
# Set all components to -1 (background)
|
|
1769
|
+
self._halfedges['component'] = -1
|
|
1770
|
+
self._vertices['component'] = -1
|
|
1771
|
+
self._faces['component'] = -1
|
|
1772
|
+
|
|
1773
|
+
# Connect component by vertex
|
|
1774
|
+
component = 0
|
|
1775
|
+
for _vertex in np.arange(self._vertices.shape[0]):
|
|
1776
|
+
curr_vertex = self._vertices[_vertex]
|
|
1777
|
+
if (curr_vertex['halfedge'] == -1) or (curr_vertex['component'] != -1):
|
|
1778
|
+
# We only want to deal with valid, unassigned vertices
|
|
1779
|
+
continue
|
|
1780
|
+
|
|
1781
|
+
# Search vertices contains vertices connected to this vertex.
|
|
1782
|
+
# We want to see if there are other vertices connected to the
|
|
1783
|
+
# search vertices.
|
|
1784
|
+
search_vertices = []
|
|
1785
|
+
self._vertices[_vertex]['component'] = component
|
|
1786
|
+
search_vertices.append(_vertex)
|
|
1787
|
+
|
|
1788
|
+
# Find vertices connected to the search vertices
|
|
1789
|
+
while search_vertices:
|
|
1790
|
+
# Grab the latest search vertex neighbors
|
|
1791
|
+
_v = search_vertices.pop()
|
|
1792
|
+
nn = self._vertices['neighbors'][_v]
|
|
1793
|
+
# Loop over the neighbors
|
|
1794
|
+
for _edge in nn:
|
|
1795
|
+
if _edge == -1:
|
|
1796
|
+
continue
|
|
1797
|
+
# Assign the vertex component label to halfedges and faces
|
|
1798
|
+
# emanating from this vertex
|
|
1799
|
+
curr_edge = self._halfedges[_edge]
|
|
1800
|
+
if self._vertices['component'][curr_edge['vertex']] != component:
|
|
1801
|
+
search_vertices.append(curr_edge['vertex'])
|
|
1802
|
+
self._halfedges['component'][_edge] = component
|
|
1803
|
+
self._halfedges['component'][curr_edge['prev']] = component
|
|
1804
|
+
self._halfedges['component'][curr_edge['next']] = component
|
|
1805
|
+
self._faces['component'][curr_edge['face']] = component
|
|
1806
|
+
self._vertices['component'][curr_edge['vertex']] = component
|
|
1807
|
+
|
|
1808
|
+
# Increment the label as any vertices not included in this
|
|
1809
|
+
# iteration's search will be part of another component.
|
|
1810
|
+
component += 1
|
|
1811
|
+
|
|
1812
|
+
def area(self, component=-1):
|
|
1813
|
+
"""
|
|
1814
|
+
Return surface area of the mesh (component) in nm^2.
|
|
1815
|
+
"""
|
|
1816
|
+
if component == -1:
|
|
1817
|
+
faces = np.flatnonzero(self._faces['halfedge']!=-1)
|
|
1818
|
+
else:
|
|
1819
|
+
faces = np.flatnonzero(self._faces['component']==component)
|
|
1820
|
+
|
|
1821
|
+
return np.sum(self._faces['area'][faces])
|
|
1822
|
+
|
|
1823
|
+
def volume(self, component=-1):
|
|
1824
|
+
"""
|
|
1825
|
+
Return volume of the mesh (component) in nm^3.
|
|
1826
|
+
|
|
1827
|
+
Sum the signed volumes of tetrahedrons formed by the faces and the origin.
|
|
1828
|
+
|
|
1829
|
+
Cha Zhang and Tsuhan Chen. "Efficient Feature Extraction for 2D/3D Objects in Mesh Representation."
|
|
1830
|
+
In Proceedings 2001 International Conference on Image Processing (Cat. No.01CH37205), 2:935–38.
|
|
1831
|
+
Thessaloniki, Greece: IEEE, 2001. https://doi.org/10.1109/ICIP.2001.958278.
|
|
1832
|
+
"""
|
|
1833
|
+
|
|
1834
|
+
# TODO?? - more component refactoring - e.g. a component object / class / iterator?
|
|
1835
|
+
if component == -1:
|
|
1836
|
+
faces = np.flatnonzero(self._faces['halfedge']!=-1)
|
|
1837
|
+
else:
|
|
1838
|
+
faces = np.flatnonzero(self._faces['component']==component)
|
|
1839
|
+
|
|
1840
|
+
faces = self._faces['halfedge'][faces]
|
|
1841
|
+
v0 = self._halfedges['vertex'][self._halfedges['prev'][faces]]
|
|
1842
|
+
v1 = self._halfedges['vertex'][faces]
|
|
1843
|
+
v2 = self._halfedges['vertex'][self._halfedges['next'][faces]]
|
|
1844
|
+
|
|
1845
|
+
p0 = self._vertices['position'][v0]
|
|
1846
|
+
p1 = self._vertices['position'][v1]
|
|
1847
|
+
p2 = self._vertices['position'][v2]
|
|
1848
|
+
|
|
1849
|
+
t0 = p2[:,0]*p1[:,1]*p0[:,2]
|
|
1850
|
+
t1 = -p1[:,0]*p2[:,1]*p0[:,2]
|
|
1851
|
+
t2 = -p2[:,0]*p0[:,1]*p1[:,2]
|
|
1852
|
+
t3 = p0[:,0]*p2[:,1]*p1[:,2]
|
|
1853
|
+
t4 = p1[:,0]*p0[:,1]*p2[:,2]
|
|
1854
|
+
t5 = -p0[:,0]*p1[:,1]*p2[:,2]
|
|
1855
|
+
|
|
1856
|
+
volume = (1.0/6.0)*(t0+t1+t2+t3+t4+t5)
|
|
1857
|
+
|
|
1858
|
+
return volume.sum()
|
|
1859
|
+
|
|
1860
|
+
def keep_components_by_volume(self, min_size=0, max_size=1e9):
|
|
1861
|
+
# Find the connected components
|
|
1862
|
+
self.find_connected_components()
|
|
1863
|
+
|
|
1864
|
+
# Get unique component values
|
|
1865
|
+
coms = np.unique(self._faces['component'][self._faces['component']!=-1])
|
|
1866
|
+
|
|
1867
|
+
# Get volumes in nanometers^3
|
|
1868
|
+
sizes = np.array([self.volume(c) for c in coms])
|
|
1869
|
+
|
|
1870
|
+
# keep components within size range
|
|
1871
|
+
kept_coms = coms[(sizes > min_size) & (sizes < max_size)]
|
|
1872
|
+
|
|
1873
|
+
self.keep_components(kept_coms)
|
|
1874
|
+
|
|
1875
|
+
|
|
1876
|
+
def keep_largest_connected_component(self, n=1):
|
|
1877
|
+
# Find the connected components
|
|
1878
|
+
self.find_connected_components()
|
|
1879
|
+
|
|
1880
|
+
# Get n largest connected components
|
|
1881
|
+
com, counts = np.unique(self._vertices['component'][self._vertices['component']!=-1], return_counts=True)
|
|
1882
|
+
# max_count = np.argmax(counts)
|
|
1883
|
+
counts_sorted = np.argsort(counts)
|
|
1884
|
+
# max_com = com[max_count]
|
|
1885
|
+
max_coms = com[counts_sorted[-n:]]
|
|
1886
|
+
|
|
1887
|
+
self.keep_components(max_coms)
|
|
1888
|
+
|
|
1889
|
+
def remove_inner_surfaces(self, max_mean_curvature=0.1):
|
|
1890
|
+
"""
|
|
1891
|
+
Isosurfacing of a closed membrane will often generate two surfaces - one on the
|
|
1892
|
+
inside of the structure and one on the outside. The inner and outer surfaces can be
|
|
1893
|
+
distinguished on the basis of mean curvature, with outer surfaces having +ve overall
|
|
1894
|
+
mean curvature and inside surfaces having -ve overall mean curvature. This function
|
|
1895
|
+
culls all surfaces having -ve mean curvature.
|
|
1896
|
+
|
|
1897
|
+
To avoid calculating component curvatures twice, this function also attempts to remove
|
|
1898
|
+
spurious surfaces (such as those generated by a single voxel > threshold) which can be identified
|
|
1899
|
+
by having an excessively large +ve mean curvature.
|
|
1900
|
+
|
|
1901
|
+
Parameters
|
|
1902
|
+
----------
|
|
1903
|
+
|
|
1904
|
+
max_mean_curvature: float [default=0.1 nm-1]
|
|
1905
|
+
a mean curvature value above which a surface is deemed to be spurious
|
|
1906
|
+
|
|
1907
|
+
Returns
|
|
1908
|
+
-------
|
|
1909
|
+
|
|
1910
|
+
"""
|
|
1911
|
+
comps = np.unique(self.component) #find the unique components
|
|
1912
|
+
comps = comps[comps >=0] #discard the -1, or 'invalid' component
|
|
1913
|
+
|
|
1914
|
+
cc = [(i, np.median(self.curvature_mean[self.component==i])) for i in comps]
|
|
1915
|
+
self.keep_components([i for i, c in cc if (c > 0) and (c < max_mean_curvature)])
|
|
1916
|
+
|
|
1917
|
+
|
|
1918
|
+
def keep_components(self, components):
|
|
1919
|
+
"""
|
|
1920
|
+
Discard all but the indicated components. Used for pruning the mesh of both
|
|
1921
|
+
small objects, and inner surfaces.
|
|
1922
|
+
|
|
1923
|
+
Parameters
|
|
1924
|
+
----------
|
|
1925
|
+
components : ndarray / list of int
|
|
1926
|
+
labels of components to keep
|
|
1927
|
+
|
|
1928
|
+
Returns
|
|
1929
|
+
-------
|
|
1930
|
+
|
|
1931
|
+
"""
|
|
1932
|
+
components = np.array(components)
|
|
1933
|
+
|
|
1934
|
+
# Remove the smaller components
|
|
1935
|
+
# _vertices = np.where((self._vertices['component'] != max_com))[0]
|
|
1936
|
+
_vertices = np.flatnonzero((self._vertices['component'][:,None] != components[None, :]).prod(1))
|
|
1937
|
+
# _edges = np.where((self._halfedges['component'] != max_com))[0]
|
|
1938
|
+
_edges = np.flatnonzero((self._halfedges['component'][:,None] != components[None, :]).prod(1))
|
|
1939
|
+
_edges_with_twins = _edges[self._halfedges['twin'][_edges] != -1]
|
|
1940
|
+
# _faces = np.where((self._faces['component'] != max_com))[0]
|
|
1941
|
+
_faces = np.flatnonzero((self._faces['component'][:,None] != components[None, :]).prod(1))
|
|
1942
|
+
|
|
1943
|
+
# Maybe don't delete some of these? (If there are singular elements in our mesh.)
|
|
1944
|
+
_kept_edges = np.array(list(set(self._halfedges['twin'][_edges_with_twins])-set(_edges)))
|
|
1945
|
+
if np.any(_kept_edges):
|
|
1946
|
+
_deleted_vertices = np.array(list(set(_vertices)-set(self._halfedges['vertex'][_kept_edges])))
|
|
1947
|
+
self._halfedges['twin'][_kept_edges] = -1
|
|
1948
|
+
else:
|
|
1949
|
+
_deleted_vertices = _vertices
|
|
1950
|
+
|
|
1951
|
+
# Delete vertices
|
|
1952
|
+
self._vertices[_deleted_vertices] = -1
|
|
1953
|
+
self._halfedges[_edges] = -1
|
|
1954
|
+
self._faces[_faces] = -1
|
|
1955
|
+
|
|
1956
|
+
# Mark deleted faces as available
|
|
1957
|
+
self._vertex_vacancies = list(set(self._vertex_vacancies).union(set(_deleted_vertices)))
|
|
1958
|
+
self._halfedge_vacancies = list(set(self._halfedge_vacancies).union(set(_edges)))
|
|
1959
|
+
self._face_vacancies = list(set(self._face_vacancies).union(set(_faces)))
|
|
1960
|
+
|
|
1961
|
+
if np.any(_kept_edges):
|
|
1962
|
+
# Update newly orphaned edges
|
|
1963
|
+
self._update_face_normals(list(set(self._halfedges['face'][_kept_edges])))
|
|
1964
|
+
self._update_vertex_neighbors(list(set(self._halfedges['vertex'][_kept_edges])))
|
|
1965
|
+
|
|
1966
|
+
self._invalidate_cached_properties()
|
|
1967
|
+
|
|
1968
|
+
cdef _find_boundary_polygons(self, np.ndarray boundary_polygons, np.ndarray boundary_edges):
|
|
1969
|
+
"""
|
|
1970
|
+
Return array of closed boundary polygons.
|
|
1971
|
+
|
|
1972
|
+
We assume boundary_edges is the exhaustive list of halfedges on a boundary.
|
|
1973
|
+
"""
|
|
1974
|
+
cdef int n_edges = boundary_edges.shape[0]
|
|
1975
|
+
cdef int max_iters = 6*n_edges # FIXME: Not clear what this upper bound should be. Seems low.
|
|
1976
|
+
cdef int i, j, k, poly, safety, n_poly, new_poly, n_left
|
|
1977
|
+
cdef int32_t _orig, _curr, _twin
|
|
1978
|
+
cdef halfedge_t *curr_edge
|
|
1979
|
+
|
|
1980
|
+
# keep track of already visited edges in components
|
|
1981
|
+
# later, keep track of vertex valences in components
|
|
1982
|
+
# this is predicated on vertices showing up in only one polygon,
|
|
1983
|
+
# which should be the case if we have removed singularities
|
|
1984
|
+
self._components_valid = 0
|
|
1985
|
+
for i in range(n_edges):
|
|
1986
|
+
curr_edge = &self._chalfedges[boundary_edges[i]]
|
|
1987
|
+
curr_edge.component = 0
|
|
1988
|
+
self._cvertices[curr_edge.vertex].component = 0
|
|
1989
|
+
|
|
1990
|
+
poly = 0 # polygon number
|
|
1991
|
+
|
|
1992
|
+
# loop over all of the boundary edges
|
|
1993
|
+
for i in range(n_edges):
|
|
1994
|
+
if (self._chalfedges[boundary_edges[i]].component == 1):
|
|
1995
|
+
continue
|
|
1996
|
+
|
|
1997
|
+
# if this edge is not included in existing polygons, create a new one
|
|
1998
|
+
k = 0 # element in polygon
|
|
1999
|
+
_curr = boundary_edges[i]
|
|
2000
|
+
_orig = _curr
|
|
2001
|
+
|
|
2002
|
+
# traverse around boundary until we return to _orig
|
|
2003
|
+
safety = 0 # safety counter
|
|
2004
|
+
while True:
|
|
2005
|
+
if safety >= max_iters:
|
|
2006
|
+
# Safety release valve so we don't iterate forever
|
|
2007
|
+
print("ERROR: boundary not found")
|
|
2008
|
+
break
|
|
2009
|
+
|
|
2010
|
+
curr_edge = &self._chalfedges[_curr]
|
|
2011
|
+
_twin = curr_edge.twin
|
|
2012
|
+
|
|
2013
|
+
if _twin == -1:
|
|
2014
|
+
if k >= n_edges:
|
|
2015
|
+
print("ERROR: Not enough space allocated for this polygon.")
|
|
2016
|
+
break
|
|
2017
|
+
# we're on a boundary, mark it as part of the polygon...
|
|
2018
|
+
boundary_polygons[poly,k] = _curr
|
|
2019
|
+
curr_edge.component = 1
|
|
2020
|
+
# ...also increment the number of incident boundary edges on its vertices
|
|
2021
|
+
self._cvertices[curr_edge.vertex].component += 1
|
|
2022
|
+
self._cvertices[self._chalfedges[curr_edge.prev].vertex].component += 1
|
|
2023
|
+
|
|
2024
|
+
k += 1
|
|
2025
|
+
|
|
2026
|
+
# ...then traverse
|
|
2027
|
+
_curr = curr_edge.next
|
|
2028
|
+
else:
|
|
2029
|
+
# traverse
|
|
2030
|
+
_curr = self._chalfedges[_twin].next
|
|
2031
|
+
|
|
2032
|
+
if _curr == _orig:
|
|
2033
|
+
# we closed the loop
|
|
2034
|
+
break
|
|
2035
|
+
|
|
2036
|
+
safety += 1
|
|
2037
|
+
|
|
2038
|
+
if k == 0:
|
|
2039
|
+
print("WARNING: Polygon of length 0????")
|
|
2040
|
+
continue
|
|
2041
|
+
elif k == 1:
|
|
2042
|
+
print("WARNING: Polygons should be of length >= 2. Perhaps there is a singularity in the mesh?")
|
|
2043
|
+
poly += 1
|
|
2044
|
+
else:
|
|
2045
|
+
# Deal with pinched polygons
|
|
2046
|
+
# We keep disconnecting pinched polygons from the original polygon and then checking the
|
|
2047
|
+
# disconnected polygon for further pinches. The function _disconnect_pinched_polygons is
|
|
2048
|
+
# set up s.t. there will be no pinched polygons in the poly entry once run, only in the
|
|
2049
|
+
# disconnected polygon in a later entry
|
|
2050
|
+
#
|
|
2051
|
+
# TODO: Is this necessary? A pinched polygon requires a singular vertex. We should simply
|
|
2052
|
+
# call _remove_singularities() before running this function.
|
|
2053
|
+
n_poly = poly
|
|
2054
|
+
n_left = 1
|
|
2055
|
+
while n_left > 0:
|
|
2056
|
+
n_left -= 1
|
|
2057
|
+
new_poly = self._disconnect_pinched_polygons(boundary_polygons, poly, n_poly, k)
|
|
2058
|
+
n_left += new_poly
|
|
2059
|
+
n_poly += new_poly
|
|
2060
|
+
poly += 1 # net increment poly by the number of new polygons we created un-pinching
|
|
2061
|
+
|
|
2062
|
+
|
|
2063
|
+
cdef int _disconnect_pinched_polygons(self, np.ndarray boundary_polygons, int curr_poly, int n_poly, int n_edges):
|
|
2064
|
+
cdef int32_t _orig, _curr
|
|
2065
|
+
cdef int j, jj, kk, n_new_polygons
|
|
2066
|
+
cdef halfedge_t *curr_edge
|
|
2067
|
+
|
|
2068
|
+
_orig = -1
|
|
2069
|
+
n_new_polygons = 0
|
|
2070
|
+
|
|
2071
|
+
# check to see if any vertices have more than one boundary edge incident on a vertex
|
|
2072
|
+
j = 0
|
|
2073
|
+
while boundary_polygons[curr_poly,j] != -1:
|
|
2074
|
+
curr_edge = &self._chalfedges[boundary_polygons[curr_poly,j]]
|
|
2075
|
+
if self._cvertices[curr_edge.vertex].component > 2:
|
|
2076
|
+
# we've hit the case were the boundary pinches in on itself. the twin->next traverse allows this
|
|
2077
|
+
# (picture a square boundary with a triangular boundary coming off one of its corners).
|
|
2078
|
+
|
|
2079
|
+
# the good news is everything is wound consistently, so we just have to clip from here to the next
|
|
2080
|
+
# instance of this vertex as a new polygon.
|
|
2081
|
+
|
|
2082
|
+
if (_orig != -1) and (curr_edge.vertex == _orig):
|
|
2083
|
+
# we previously marked this vertex, finish splitting this into a new polygon
|
|
2084
|
+
n_new_polygons += 1 # add another polygon
|
|
2085
|
+
|
|
2086
|
+
print(f"New polygon: {_orig} {self._chalfedges[kk].vertex}")
|
|
2087
|
+
print(f"{boundary_polygons[curr_poly,:14]}")
|
|
2088
|
+
for jj in range(kk,j+1):
|
|
2089
|
+
# copy into the new polygon
|
|
2090
|
+
boundary_polygons[(n_poly+n_new_polygons),(jj-kk)] = boundary_polygons[curr_poly,jj]
|
|
2091
|
+
# slide everything in the current polygon backwards (or delete it)
|
|
2092
|
+
for jj in range(j+1,n_edges+1):
|
|
2093
|
+
boundary_polygons[curr_poly,jj-j+kk-1] = boundary_polygons[curr_poly,jj]
|
|
2094
|
+
for jj in range(n_edges-j+kk,n_edges+1):
|
|
2095
|
+
boundary_polygons[curr_poly,jj] = -1
|
|
2096
|
+
#for jj in range(kk,j):
|
|
2097
|
+
# if (j+jj) > n_edges:
|
|
2098
|
+
# boundary_polygons[curr_poly,jj] = -1
|
|
2099
|
+
# else:
|
|
2100
|
+
# boundary_polygons[curr_poly,jj] = boundary_polygons[curr_poly,(j+jj)]
|
|
2101
|
+
# boundary_polygons[curr_poly,(j+jj)] = -1 # restore to -1
|
|
2102
|
+
print(f"{boundary_polygons[curr_poly,:14]}")
|
|
2103
|
+
print(f"{boundary_polygons[(n_poly+n_new_polygons),:14]}")
|
|
2104
|
+
# we moved everything backwards so we need to re-check this slot, which now contains a new value
|
|
2105
|
+
# j -= 1
|
|
2106
|
+
|
|
2107
|
+
# decrement the number of pinches (there might be more than one, picture two triangluar boundaries
|
|
2108
|
+
# coming off the same corner of a square boundary)
|
|
2109
|
+
self._cvertices[curr_edge.vertex].component -= 1
|
|
2110
|
+
_orig = -1
|
|
2111
|
+
else:
|
|
2112
|
+
_orig = curr_edge.vertex # now we use this to track vertex
|
|
2113
|
+
kk = j+1 # use this to track starting index
|
|
2114
|
+
j += 1
|
|
2115
|
+
if j >= n_edges:
|
|
2116
|
+
break
|
|
2117
|
+
|
|
2118
|
+
return n_new_polygons
|
|
2119
|
+
|
|
2120
|
+
cdef _pinch_boundaries(self, np.ndarray boundary_polygons, bint live_update=1):
|
|
2121
|
+
"""
|
|
2122
|
+
Take an array of boundary polygons (each row is a polygon) and pinch them to close.
|
|
2123
|
+
|
|
2124
|
+
NOTE: This implementation seems to produce singularities.
|
|
2125
|
+
|
|
2126
|
+
References
|
|
2127
|
+
----------
|
|
2128
|
+
Gueziec et al. Cutting and Stitching: Converting Sets of Polygons
|
|
2129
|
+
to Manifold Surfaces, IEEE TRANSACTIONS ON VISUALIZATION AND
|
|
2130
|
+
COMPUTER GRAPHICS, 2001.
|
|
2131
|
+
"""
|
|
2132
|
+
cdef int j, k, n_edges, n_pinch
|
|
2133
|
+
cdef bint odd
|
|
2134
|
+
cdef int32_t _edge0, _edge1
|
|
2135
|
+
cdef int n_poly = boundary_polygons.shape[0]
|
|
2136
|
+
cdef int max_edges = boundary_polygons.shape[1]
|
|
2137
|
+
|
|
2138
|
+
# Move down the row of polygons
|
|
2139
|
+
j = 0
|
|
2140
|
+
while (j < n_poly) and (boundary_polygons[j,0] != -1):
|
|
2141
|
+
#if j != 1:
|
|
2142
|
+
# j += 1
|
|
2143
|
+
# continue
|
|
2144
|
+
# Count the number of elements in this polygon
|
|
2145
|
+
n_edges = 0
|
|
2146
|
+
while (n_edges < max_edges) and (boundary_polygons[j,n_edges] != -1):
|
|
2147
|
+
n_edges += 1
|
|
2148
|
+
|
|
2149
|
+
if n_edges < 2:
|
|
2150
|
+
print("ERROR: The absolute minimum polygon size is 2. Something's wrong. Skipping.")
|
|
2151
|
+
continue
|
|
2152
|
+
|
|
2153
|
+
#if n_edges > 6:
|
|
2154
|
+
# print(f"Skipping polygon {j} of size {n_edges}")
|
|
2155
|
+
# j += 1
|
|
2156
|
+
# continue
|
|
2157
|
+
|
|
2158
|
+
#print(f"Polygon {j} of size {n_edges}")
|
|
2159
|
+
|
|
2160
|
+
odd = ((n_edges % 2) != 0)
|
|
2161
|
+
|
|
2162
|
+
if odd:
|
|
2163
|
+
# If the length of this polygon is odd, we pinch all but the last three edges...
|
|
2164
|
+
n_pinch = (n_edges-3)//2
|
|
2165
|
+
else:
|
|
2166
|
+
# ...otherwise we pinch every edge
|
|
2167
|
+
n_pinch = (n_edges)//2
|
|
2168
|
+
|
|
2169
|
+
# Now pinch elements together
|
|
2170
|
+
#k = 2
|
|
2171
|
+
for k in range(n_pinch):
|
|
2172
|
+
# Run around the closed boundary loop in opposite directions
|
|
2173
|
+
_edge0 = boundary_polygons[j,k]
|
|
2174
|
+
_edge1 = boundary_polygons[j,n_edges-k-1]
|
|
2175
|
+
|
|
2176
|
+
self._pinch_edges(_edge0, _edge1, live_update=live_update)
|
|
2177
|
+
|
|
2178
|
+
if odd:
|
|
2179
|
+
#print("We went odd!")
|
|
2180
|
+
# Make a triangle from the last three edges to seal the boundary
|
|
2181
|
+
self._fill_triangle(boundary_polygons[j,n_pinch], boundary_polygons[j,n_pinch+1], boundary_polygons[j,n_pinch+2], live_update=live_update)
|
|
2182
|
+
|
|
2183
|
+
j += 1
|
|
2184
|
+
|
|
2185
|
+
self._invalidate_cached_properties()
|
|
2186
|
+
|
|
2187
|
+
cdef _pinch_edges(self, int32_t _edge0, int32_t _edge1, bint live_update=1):
|
|
2188
|
+
"""
|
|
2189
|
+
Pinch two (boundary) edges together to stitch part of the mesh.
|
|
2190
|
+
"""
|
|
2191
|
+
cdef int i
|
|
2192
|
+
cdef int32_t _vertex0, _vertex1
|
|
2193
|
+
cdef vertex_d *vertex0
|
|
2194
|
+
cdef vertex_d *vertex1
|
|
2195
|
+
cdef float pos0, pos1, pos2
|
|
2196
|
+
cdef halfedge_t *edge0
|
|
2197
|
+
cdef halfedge_t *edge1
|
|
2198
|
+
|
|
2199
|
+
cdef int n_halfedges = self._halfedges.shape[0]
|
|
2200
|
+
|
|
2201
|
+
edge0 = &self._chalfedges[_edge0]
|
|
2202
|
+
edge1 = &self._chalfedges[_edge1]
|
|
2203
|
+
|
|
2204
|
+
if (edge0.twin != -1) or (edge1.twin != -1):
|
|
2205
|
+
print("WARNING: One of these is not a boundary!!! Are you sure you want to stitch them?")
|
|
2206
|
+
|
|
2207
|
+
if (edge1.vertex != self._chalfedges[edge0.prev].vertex):
|
|
2208
|
+
print("WARNING: These are not continuous!")
|
|
2209
|
+
|
|
2210
|
+
# update pinched vertex position
|
|
2211
|
+
_vertex0 = edge0.vertex
|
|
2212
|
+
_vertex1 = self._chalfedges[edge1.prev].vertex
|
|
2213
|
+
|
|
2214
|
+
#print(f"Check vertices: {_vertex1} {edge1.vertex} {self._chalfedges[edge0.prev].vertex} {_vertex0}")
|
|
2215
|
+
#print("Pre-stitch")
|
|
2216
|
+
#print(edge0.vertex, edge0.face, edge0.twin, edge0.next, edge0.prev, edge0.length, edge0.component, edge0.locally_manifold)
|
|
2217
|
+
#print(edge1.vertex, edge1.face, edge1.twin, edge1.next, edge1.prev, edge1.length, edge1.component, edge1.locally_manifold)
|
|
2218
|
+
|
|
2219
|
+
if _vertex0 != _vertex1:
|
|
2220
|
+
vertex0 = &self._cvertices[_vertex0]
|
|
2221
|
+
vertex1 = &self._cvertices[_vertex1]
|
|
2222
|
+
pos0 = 0.5*(vertex0.position0 + vertex1.position0)
|
|
2223
|
+
pos1 = 0.5*(vertex0.position1 + vertex1.position1)
|
|
2224
|
+
pos2 = 0.5*(vertex0.position2 + vertex1.position2)
|
|
2225
|
+
vertex0.position0 = pos0
|
|
2226
|
+
vertex0.position1 = pos1
|
|
2227
|
+
vertex0.position2 = pos2
|
|
2228
|
+
|
|
2229
|
+
# set all vertex0 points to vertex1
|
|
2230
|
+
for i in range(n_halfedges):
|
|
2231
|
+
if self._chalfedges[i].vertex == _vertex1:
|
|
2232
|
+
self._chalfedges[i].vertex = _vertex0
|
|
2233
|
+
|
|
2234
|
+
# Delete this vertex
|
|
2235
|
+
self._vertex_delete(_vertex1)
|
|
2236
|
+
|
|
2237
|
+
# update connectivity
|
|
2238
|
+
edge0.twin = _edge1
|
|
2239
|
+
edge1.twin = _edge0
|
|
2240
|
+
|
|
2241
|
+
if live_update:
|
|
2242
|
+
update_face_normal(int(edge0.face), self._chalfedges, self._cvertices, self._cfaces)
|
|
2243
|
+
update_face_normal(int(edge1.face), self._chalfedges, self._cvertices, self._cfaces)
|
|
2244
|
+
|
|
2245
|
+
update_single_vertex_neighbours(int(edge0.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2246
|
+
update_single_vertex_neighbours(int(edge1.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2247
|
+
|
|
2248
|
+
#print("Post-stitch")
|
|
2249
|
+
#print(edge0.vertex, edge0.face, edge0.twin, edge0.next, edge0.prev, edge0.length, edge0.component, edge0.locally_manifold)
|
|
2250
|
+
#print(edge1.vertex, edge1.face, edge1.twin, edge1.next, edge1.prev, edge1.length, edge1.component, edge1.locally_manifold)
|
|
2251
|
+
self._singular_edges_valid = 0
|
|
2252
|
+
#print(self.singular_edges)
|
|
2253
|
+
|
|
2254
|
+
#for i in range(self.singular_edges.shape[0]):
|
|
2255
|
+
# edge0 = &self._chalfedges[self.singular_edges[i]]
|
|
2256
|
+
# print(edge0.vertex, edge0.face, edge0.twin, edge0.next, edge0.prev, edge0.length, edge0.component, edge0.locally_manifold)
|
|
2257
|
+
|
|
2258
|
+
cdef _color_boundaries(self, np.ndarray boundary_polygons):
|
|
2259
|
+
"""
|
|
2260
|
+
Color each boundary a single color.
|
|
2261
|
+
"""
|
|
2262
|
+
cdef int j, n_edges
|
|
2263
|
+
|
|
2264
|
+
self._components_valid = 1
|
|
2265
|
+
|
|
2266
|
+
# zero-out components
|
|
2267
|
+
for j in range(self._vertices.shape[0]):
|
|
2268
|
+
self._cvertices[j].component = -1
|
|
2269
|
+
|
|
2270
|
+
# Move down the row of polygons
|
|
2271
|
+
j = 0
|
|
2272
|
+
while boundary_polygons[j,0] != -1:
|
|
2273
|
+
# Count the number of elements in this polygon
|
|
2274
|
+
n_edges = 0
|
|
2275
|
+
#if j != 1:
|
|
2276
|
+
# j+=1
|
|
2277
|
+
# continue
|
|
2278
|
+
while boundary_polygons[j,n_edges] != -1:
|
|
2279
|
+
#self._cvertices[self._chalfedges[self._chalfedges[boundary_polygons[j,n_edges]].prev].vertex].component
|
|
2280
|
+
self._cvertices[self._chalfedges[boundary_polygons[j,n_edges]].vertex].component = j # n_edges
|
|
2281
|
+
#self._cvertices[self._chalfedges[boundary_polygons[j,n_edges]].vertex].component = n_edges
|
|
2282
|
+
n_edges += 1
|
|
2283
|
+
j += 1
|
|
2284
|
+
|
|
2285
|
+
def _fill_triangle(self, int32_t h0, int32_t h1, int32_t h2, bint live_update=1):
|
|
2286
|
+
"""
|
|
2287
|
+
Make a triangle from the three open edges to seal a boundary
|
|
2288
|
+
"""
|
|
2289
|
+
cdef halfedge_t *edge0
|
|
2290
|
+
cdef halfedge_t *edge1
|
|
2291
|
+
cdef halfedge_t *edge2
|
|
2292
|
+
|
|
2293
|
+
n_edges = self.new_edges(3)
|
|
2294
|
+
n_faces = self.new_faces(1)
|
|
2295
|
+
|
|
2296
|
+
edge0 = &self._chalfedges[h0]
|
|
2297
|
+
edge1 = &self._chalfedges[h1]
|
|
2298
|
+
edge2 = &self._chalfedges[h2]
|
|
2299
|
+
|
|
2300
|
+
self._populate_edge(n_edges[0], edge2.vertex, prev=n_edges[1], next=n_edges[2], face=n_faces[0], twin=h0)
|
|
2301
|
+
self._populate_edge(n_edges[1], edge0.vertex, prev=n_edges[2], next=n_edges[0], face=n_faces[0], twin=h1)
|
|
2302
|
+
self._populate_edge(n_edges[2], edge1.vertex, prev=n_edges[0], next=n_edges[1], face=n_faces[0], twin=h2)
|
|
2303
|
+
|
|
2304
|
+
self._chalfedges[h0].twin = n_edges[0]
|
|
2305
|
+
self._chalfedges[h1].twin = n_edges[1]
|
|
2306
|
+
self._chalfedges[h2].twin = n_edges[2]
|
|
2307
|
+
|
|
2308
|
+
self._cfaces[n_faces[0]].halfedge = n_edges[0]
|
|
2309
|
+
|
|
2310
|
+
if live_update:
|
|
2311
|
+
update_face_normal(int(n_faces[0]), self._chalfedges, self._cvertices, self._cfaces)
|
|
2312
|
+
|
|
2313
|
+
update_single_vertex_neighbours(int(edge2.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2314
|
+
update_single_vertex_neighbours(int(edge1.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2315
|
+
update_single_vertex_neighbours(int(edge0.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2316
|
+
|
|
2317
|
+
cdef _zig_zag_triangulation(self, np.ndarray boundary_polygons, int32_t *n_edges, int32_t *n_faces, int row, int len_poly, bint live_update=1):
|
|
2318
|
+
"""
|
|
2319
|
+
Triangulate a (boundary) polygon (an array of halfedges) by zig-zagging
|
|
2320
|
+
between its edges.
|
|
2321
|
+
"""
|
|
2322
|
+
cdef int k
|
|
2323
|
+
cdef int _edge0, _edge1
|
|
2324
|
+
cdef halfedge_t *edge0
|
|
2325
|
+
cdef halfedge_t *edge1
|
|
2326
|
+
cdef bint zig
|
|
2327
|
+
|
|
2328
|
+
# How many edges are in this polygon?
|
|
2329
|
+
# len_poly = 0
|
|
2330
|
+
# while poly[len_poly] != -1:
|
|
2331
|
+
# print(poly[len_poly])
|
|
2332
|
+
# len_poly += 1
|
|
2333
|
+
|
|
2334
|
+
if len_poly < 3:
|
|
2335
|
+
print("ERROR: Cannot triangulate polygons with length less than 3. Aborting.")
|
|
2336
|
+
|
|
2337
|
+
# starting edges
|
|
2338
|
+
_edge0 = boundary_polygons[row,0]
|
|
2339
|
+
_edge1 = boundary_polygons[row,1]
|
|
2340
|
+
|
|
2341
|
+
# Zig-zag with the new edges
|
|
2342
|
+
zig = 0
|
|
2343
|
+
for k in range(len_poly-2):
|
|
2344
|
+
#print(k, _edge0, _edge1)
|
|
2345
|
+
# grab the two existing edges on the polygon
|
|
2346
|
+
edge0 = &self._chalfedges[_edge0]
|
|
2347
|
+
edge1 = &self._chalfedges[_edge1]
|
|
2348
|
+
|
|
2349
|
+
#print(edge0.vertex, edge0.face, edge0.twin, edge0.next, edge0.prev, edge0.length, edge0.component, edge0.locally_manifold)
|
|
2350
|
+
#print(edge1.vertex, edge1.face, edge1.twin, edge1.next, edge1.prev, edge1.length, edge1.component, edge1.locally_manifold)
|
|
2351
|
+
|
|
2352
|
+
# Populate the new triangle
|
|
2353
|
+
self._populate_edge(n_edges[3*k], self._chalfedges[edge0.prev].vertex, prev=n_edges[3*k+1], next=n_edges[3*k+2], face=n_faces[k], twin=_edge0)
|
|
2354
|
+
self._populate_edge(n_edges[3*k+1], edge0.vertex, prev=n_edges[3*k+2], next=n_edges[3*k], face=n_faces[k], twin=_edge1)
|
|
2355
|
+
self._populate_edge(n_edges[3*k+2], edge1.vertex, prev=n_edges[3*k], next=n_edges[3*k+1], face=n_faces[k], twin=-1)
|
|
2356
|
+
self._cfaces[n_faces[k]].halfedge = n_edges[3*k]
|
|
2357
|
+
|
|
2358
|
+
# stitch to previously-existing edges
|
|
2359
|
+
self._chalfedges[_edge0].twin = n_edges[3*k]
|
|
2360
|
+
self._chalfedges[_edge1].twin = n_edges[3*k+1]
|
|
2361
|
+
|
|
2362
|
+
if live_update:
|
|
2363
|
+
update_face_normal(int(n_faces[k]), self._chalfedges, self._cvertices, self._cfaces)
|
|
2364
|
+
|
|
2365
|
+
update_single_vertex_neighbours(int(self._chalfedges[edge0.prev].vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2366
|
+
update_single_vertex_neighbours(int(edge0.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2367
|
+
update_single_vertex_neighbours(int(edge1.vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2368
|
+
|
|
2369
|
+
if k >= (len_poly-3):
|
|
2370
|
+
# don't index beyond the length of new_edge
|
|
2371
|
+
break
|
|
2372
|
+
|
|
2373
|
+
# Choose the next two edges to stitch
|
|
2374
|
+
if zig:
|
|
2375
|
+
_edge0 = n_edges[3*k+2]
|
|
2376
|
+
_edge1 = boundary_polygons[row,(k+1)//2+1]
|
|
2377
|
+
else:
|
|
2378
|
+
_edge0 = boundary_polygons[row,len_poly-k//2-1]
|
|
2379
|
+
_edge1 = n_edges[3*k+2]
|
|
2380
|
+
zig = 1-zig
|
|
2381
|
+
|
|
2382
|
+
# stitch the last edge
|
|
2383
|
+
_edge0 = boundary_polygons[row,len_poly-(len_poly-2)//2-1]
|
|
2384
|
+
_edge1 = n_edges[3*(len_poly-3)+2]
|
|
2385
|
+
self._chalfedges[_edge0].twin = _edge1
|
|
2386
|
+
self._chalfedges[_edge1].twin = _edge0
|
|
2387
|
+
|
|
2388
|
+
if live_update:
|
|
2389
|
+
update_single_vertex_neighbours(int(self._chalfedges[_edge0].vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2390
|
+
update_single_vertex_neighbours(int(self._chalfedges[_edge1].vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2391
|
+
|
|
2392
|
+
def _fill_holes(self, np.ndarray boundary_polygons, bint live_update=1):
|
|
2393
|
+
"""
|
|
2394
|
+
Fill in the mesh holes: closed loops of boundary edges, one per row in boundary_polygons.
|
|
2395
|
+
"""
|
|
2396
|
+
cdef int j, n_edges
|
|
2397
|
+
cdef int n_poly = boundary_polygons.shape[0]
|
|
2398
|
+
cdef int max_edges = boundary_polygons.shape[1]
|
|
2399
|
+
|
|
2400
|
+
j = 0
|
|
2401
|
+
while (j < n_poly) and (boundary_polygons[j,0] != -1):
|
|
2402
|
+
# Count the number of elements in this polygon
|
|
2403
|
+
n_edges = 0
|
|
2404
|
+
while (n_edges < max_edges) and (boundary_polygons[j,n_edges] != -1):
|
|
2405
|
+
n_edges += 1
|
|
2406
|
+
|
|
2407
|
+
#print(f"Polygon {j} of length {n_edges}")
|
|
2408
|
+
|
|
2409
|
+
if n_edges == 2:
|
|
2410
|
+
self._pinch_edges(boundary_polygons[j,0], boundary_polygons[j,1], live_update=live_update)
|
|
2411
|
+
else:
|
|
2412
|
+
# Initialize new edges/faces
|
|
2413
|
+
new_faces = self.new_faces(int(n_edges-2))
|
|
2414
|
+
new_edges = self.new_edges(int(3*(n_edges-3)+3))
|
|
2415
|
+
|
|
2416
|
+
# Right now we use zig-zag, but we should use Liepa, Peter. "Filling Holes in Meshes."
|
|
2417
|
+
# In Proceedings of the 2003 Eurographics/ACM SIGGRAPH Symposium on Geometry Processing,
|
|
2418
|
+
# 200-205. SGP '03. Goslar, DEU: Eurographics Association, 2003.
|
|
2419
|
+
|
|
2420
|
+
self._zig_zag_triangulation(boundary_polygons,
|
|
2421
|
+
<int32_t *> np.PyArray_DATA(new_edges),
|
|
2422
|
+
<int32_t *> np.PyArray_DATA(new_faces),
|
|
2423
|
+
j, n_edges, live_update=live_update)
|
|
2424
|
+
|
|
2425
|
+
j += 1
|
|
2426
|
+
|
|
2427
|
+
def _remove_singularities(self):
|
|
2428
|
+
"""
|
|
2429
|
+
Excise singularities from the mesh. This essentially unzips multivalent
|
|
2430
|
+
edges from the rest of the mesh, creating new, smaller connected
|
|
2431
|
+
components, each of which is free of singularities. This follows the
|
|
2432
|
+
local method for cutting outlined in Gueziec et al.
|
|
2433
|
+
|
|
2434
|
+
References
|
|
2435
|
+
----------
|
|
2436
|
+
Gueziec et al. Cutting and Stitching: Converting Sets of Polygons
|
|
2437
|
+
to Manifold Surfaces, IEEE TRANSACTIONS ON VISUALIZATION AND
|
|
2438
|
+
COMPUTER GRAPHICS, 2001.
|
|
2439
|
+
"""
|
|
2440
|
+
cdef int i, j, k, component, n_v
|
|
2441
|
+
cdef int32_t twin_idx, next_idx
|
|
2442
|
+
|
|
2443
|
+
# Iterate over vertices that are endpoints of a singular edge or are
|
|
2444
|
+
# isolated singular vertices (number of incident edges =/= number of
|
|
2445
|
+
# neighbors)
|
|
2446
|
+
self._singular_vertices_valid = 0
|
|
2447
|
+
self._singular_edges_valid = 0
|
|
2448
|
+
singular_vertices = self.singular_vertices
|
|
2449
|
+
|
|
2450
|
+
for i in range(singular_vertices.shape[0]):
|
|
2451
|
+
# Find the star of this vertex. That is, all of the vertices
|
|
2452
|
+
# edges incident on this vertex. We grab only the edges pointing at the vertex
|
|
2453
|
+
# because 1) this is sufficient and 2) this allows unambiguous traversal of
|
|
2454
|
+
# the one-ring neighbor via twin->next movement
|
|
2455
|
+
star = np.flatnonzero(self._halfedges['vertex'] == singular_vertices[i]).astype('i4')
|
|
2456
|
+
|
|
2457
|
+
# Partition the faces of the star s.t. two faces are connected if
|
|
2458
|
+
# they share a non-singular edge. Denote the number of connected
|
|
2459
|
+
# components under this relation as component.
|
|
2460
|
+
for j in range(star.shape[0]):
|
|
2461
|
+
self._chalfedges[star[j]].component = -1
|
|
2462
|
+
self._chalfedges[self._chalfedges[star[j]].next].component = -1
|
|
2463
|
+
self._chalfedges[self._chalfedges[star[j]].prev].component = -1
|
|
2464
|
+
component = 0
|
|
2465
|
+
for j in range(star.shape[0]):
|
|
2466
|
+
component = flood_fill_star_component(star[j], component, self._chalfedges)
|
|
2467
|
+
|
|
2468
|
+
# Construct n_v = component-1 additional compies of vertex i...
|
|
2469
|
+
n_v = component-1
|
|
2470
|
+
if n_v < 1:
|
|
2471
|
+
continue
|
|
2472
|
+
n_vertices = self.new_vertices(n_v)
|
|
2473
|
+
for k in range(n_v):
|
|
2474
|
+
# ...with the same coordinates
|
|
2475
|
+
self._cvertices[n_vertices[k]].position0 = copy.copy(self._cvertices[singular_vertices[i]].position0) #+ 10*(k+1)*self._cvertices[singular_vertices[i]].normal0
|
|
2476
|
+
self._cvertices[n_vertices[k]].position1 = copy.copy(self._cvertices[singular_vertices[i]].position1) #+ 10*(k+1)*self._cvertices[singular_vertices[i]].normal1
|
|
2477
|
+
self._cvertices[n_vertices[k]].position2 = copy.copy(self._cvertices[singular_vertices[i]].position2) #+ 10*(k+1)*self._cvertices[singular_vertices[i]].normal2
|
|
2478
|
+
self._cvertices[n_vertices[k]].halfedge = -1
|
|
2479
|
+
|
|
2480
|
+
# Loop over the star again, assigning vertices to each component
|
|
2481
|
+
for j in range(star.shape[0]):
|
|
2482
|
+
k = self._chalfedges[star[j]].component
|
|
2483
|
+
if k < n_v:
|
|
2484
|
+
# Assign halfedges associated with component k \in range(n_v) a unique copy of this vertex
|
|
2485
|
+
self._chalfedges[star[j]].vertex = n_vertices[k]
|
|
2486
|
+
|
|
2487
|
+
# Mark singular edges in this component as non-singular, boundary
|
|
2488
|
+
if self._chalfedges[star[j]].locally_manifold == 0:
|
|
2489
|
+
#print(f"Singular in component {k}")
|
|
2490
|
+
#self._chalfedges[star[j]].locally_manifold = 1
|
|
2491
|
+
twin_idx = self._chalfedges[star[j]].twin
|
|
2492
|
+
if twin_idx != -1:
|
|
2493
|
+
self._chalfedges[twin_idx].twin = -1
|
|
2494
|
+
self._chalfedges[star[j]].twin = -1
|
|
2495
|
+
|
|
2496
|
+
next_idx = self._chalfedges[star[j]].next
|
|
2497
|
+
if self._chalfedges[next_idx].locally_manifold == 0:
|
|
2498
|
+
#print(f"Next singular in component {k}")
|
|
2499
|
+
#self._chalfedges[next_idx].locally_manifold = 1
|
|
2500
|
+
twin_idx = self._chalfedges[next_idx].twin
|
|
2501
|
+
if twin_idx != -1:
|
|
2502
|
+
self._chalfedges[twin_idx].twin = -1
|
|
2503
|
+
self._chalfedges[next_idx].twin = -1
|
|
2504
|
+
|
|
2505
|
+
# find an emanating halfedge in component k
|
|
2506
|
+
self._cvertices[self._chalfedges[star[j]].vertex].halfedge = next_idx
|
|
2507
|
+
|
|
2508
|
+
for j in range(star.shape[0]):
|
|
2509
|
+
update_face_normal(int(self._chalfedges[star[j]].face), self._chalfedges, self._cvertices, self._cfaces)
|
|
2510
|
+
update_single_vertex_neighbours(int(self._chalfedges[star[j]].vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2511
|
+
update_single_vertex_neighbours(int(self._chalfedges[self._chalfedges[star[j]].prev].vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2512
|
+
update_single_vertex_neighbours(int(self._chalfedges[self._chalfedges[star[j]].next].vertex), self._chalfedges, self._cvertices, self._cfaces)
|
|
2513
|
+
|
|
2514
|
+
# reset
|
|
2515
|
+
self._invalidate_cached_properties()
|
|
2516
|
+
|
|
2517
|
+
def repair(self, bint only_largest_component=0, bint pinch=0):
|
|
2518
|
+
"""
|
|
2519
|
+
Repair the mesh so it's topologically manifold.
|
|
2520
|
+
|
|
2521
|
+
References
|
|
2522
|
+
----------
|
|
2523
|
+
M. Attene, A lightweight approach to repairing digitized polygon
|
|
2524
|
+
meshes, The Visual Computer, 2010.
|
|
2525
|
+
"""
|
|
2526
|
+
|
|
2527
|
+
# 1. Remove singularities (singular edges and isolated singular vertices)
|
|
2528
|
+
self._remove_singularities()
|
|
2529
|
+
|
|
2530
|
+
# (Optional) 2. Remove all connected components except the largest
|
|
2531
|
+
if only_largest_component:
|
|
2532
|
+
self.keep_largest_connected_component()
|
|
2533
|
+
|
|
2534
|
+
#self.remesh(n=1)
|
|
2535
|
+
|
|
2536
|
+
# construct an array to store the boundary polygons
|
|
2537
|
+
# the longest boundary polygon possible contains all of the boundary edges in the mesh,
|
|
2538
|
+
# the shortest boundary "polygon" possible is of length 2
|
|
2539
|
+
# TODO: This could be quite large if we have a lot of boundary edges
|
|
2540
|
+
boundary_edges = np.flatnonzero((self._halfedges['vertex'] != -1) & (self._halfedges['twin'] == -1)).astype('i4')
|
|
2541
|
+
max_poly_length = max(boundary_edges.shape[0],4)
|
|
2542
|
+
boundary_polygons = -1*np.ones([max_poly_length//2, max_poly_length], dtype='i4')
|
|
2543
|
+
|
|
2544
|
+
# 3. Patch mesh holes with new triangles
|
|
2545
|
+
self._find_boundary_polygons(boundary_polygons, boundary_edges) # fill the boundary polygon array
|
|
2546
|
+
|
|
2547
|
+
if pinch == 1:
|
|
2548
|
+
self._pinch_boundaries(boundary_polygons) # close the boundary polygons by pinching
|
|
2549
|
+
else:
|
|
2550
|
+
self._fill_holes(boundary_polygons)
|
|
2551
|
+
# self._color_boundaries(boundary_polygons)
|
|
2552
|
+
|
|
2553
|
+
# Reset
|
|
2554
|
+
#self._manifold = None
|
|
2555
|
+
# self._faces['normal'][:] = -1
|
|
2556
|
+
# self._vertices['normal'][:] = -1
|
|
2557
|
+
#self._face_normals_valid = 0
|
|
2558
|
+
#self._vertex_normals_valid = 0
|
|
2559
|
+
self._invalidate_cached_properties()
|
|
2560
|
+
self._update_all_face_normals()
|
|
2561
|
+
self._update_all_vertex_neighbours()
|
|
2562
|
+
|
|
2563
|
+
def deneck(self, e_threshold=30.0):
|
|
2564
|
+
"""
|
|
2565
|
+
Remove topological necks by deleting high energy vertices and patching the holes
|
|
2566
|
+
this creates.
|
|
2567
|
+
|
|
2568
|
+
TODO - threshold is very emperical.
|
|
2569
|
+
TODO - is E actually defined here, or only in subclasses?
|
|
2570
|
+
"""
|
|
2571
|
+
verts = np.flatnonzero(self.E > e_threshold)
|
|
2572
|
+
self.unsafe_remove_vertices(verts)
|
|
2573
|
+
self.repair()
|
|
2574
|
+
|
|
2575
|
+
@cython.boundscheck(False)
|
|
2576
|
+
@cython.initializedcheck(False)
|
|
2577
|
+
@cython.wraparound(False)
|
|
2578
|
+
def unsafe_remove_vertices(self, ids_to_remove):
|
|
2579
|
+
"""
|
|
2580
|
+
Remove a vertex and incident halfedges/faces.
|
|
2581
|
+
|
|
2582
|
+
NOTE: this will leave a non-manifold mesh! We then rely on repair to tidy it up.
|
|
2583
|
+
"""
|
|
2584
|
+
|
|
2585
|
+
#ids_to_remove = [id,]
|
|
2586
|
+
|
|
2587
|
+
cdef int id, h, f, i, hvx, cand, ht, j
|
|
2588
|
+
cdef int n_vertices = self._vertices.shape[0]
|
|
2589
|
+
|
|
2590
|
+
cdef int [::1] _h_to_remove
|
|
2591
|
+
cdef int _n_h_to_remove, c_in_h_to_remove
|
|
2592
|
+
|
|
2593
|
+
assert(len(ids_to_remove) > 0)
|
|
2594
|
+
|
|
2595
|
+
h0 = np.concatenate([np.argwhere(self._halfedges['vertex']==id).squeeze() for id in ids_to_remove])
|
|
2596
|
+
h1 = self._halfedges['twin'][h0]
|
|
2597
|
+
h1 = h1[h1 != -1]
|
|
2598
|
+
|
|
2599
|
+
h_to_remove = np.concatenate((h0, h1))
|
|
2600
|
+
|
|
2601
|
+
#print([self._halfedges['face'][h] for h in h_to_remove])
|
|
2602
|
+
|
|
2603
|
+
f_to_remove = np.unique([f for f in [self._halfedges['face'][h] for h in h_to_remove] if f >=0])
|
|
2604
|
+
|
|
2605
|
+
h_to_remove = np.array([h for h in np.unique(np.concatenate([np.argwhere(self._halfedges['face'] == f).squeeze() for f in f_to_remove])) if h >=0], 'i')
|
|
2606
|
+
|
|
2607
|
+
_h_to_remove = h_to_remove
|
|
2608
|
+
_n_h_to_remove = len(h_to_remove)
|
|
2609
|
+
|
|
2610
|
+
# make sure that none of the halfedges that we remove is referenced from a vertex
|
|
2611
|
+
# that should remain
|
|
2612
|
+
for j in range(_n_h_to_remove):
|
|
2613
|
+
h = _h_to_remove[j]
|
|
2614
|
+
#try:
|
|
2615
|
+
# hvx = np.flatnonzero(self._vertices['halfedge'] == h)[0]
|
|
2616
|
+
#except IndexError:
|
|
2617
|
+
# # no vertices point to this halfedge - we don't need to do anything
|
|
2618
|
+
# continue
|
|
2619
|
+
|
|
2620
|
+
hvx = -1
|
|
2621
|
+
for i in range(n_vertices):
|
|
2622
|
+
if self._cvertices[i].halfedge == h:
|
|
2623
|
+
hvx = i
|
|
2624
|
+
break
|
|
2625
|
+
|
|
2626
|
+
if hvx == -1:
|
|
2627
|
+
# we did not find any vertex which points to this half edge
|
|
2628
|
+
continue
|
|
2629
|
+
|
|
2630
|
+
|
|
2631
|
+
if hvx in ids_to_remove:
|
|
2632
|
+
# we are going to remove the vertex, ignore
|
|
2633
|
+
continue
|
|
2634
|
+
|
|
2635
|
+
#iterate around the vertex trying to find a halfedge that will remain
|
|
2636
|
+
cand = self._chalfedges[self._chalfedges[h].twin].next
|
|
2637
|
+
|
|
2638
|
+
# cand in h_to_remove
|
|
2639
|
+
c_in_h_to_remove = 0
|
|
2640
|
+
for i in range(_n_h_to_remove):
|
|
2641
|
+
c_in_h_to_remove |= (cand == _h_to_remove[i])
|
|
2642
|
+
|
|
2643
|
+
while (cand != h) and (cand != -1) and c_in_h_to_remove:
|
|
2644
|
+
cand = self._chalfedges[self._chalfedges[cand].twin].next
|
|
2645
|
+
|
|
2646
|
+
# cand in h_to_remove
|
|
2647
|
+
c_in_h_to_remove = 0
|
|
2648
|
+
for i in range(_n_h_to_remove):
|
|
2649
|
+
c_in_h_to_remove |= (cand == _h_to_remove[i])
|
|
2650
|
+
|
|
2651
|
+
# didn't find a candidate, iterate in other direction
|
|
2652
|
+
if (cand != h) or (cand != -1) or (c_in_h_to_remove):
|
|
2653
|
+
cand = self._chalfedges[self._chalfedges[h].prev].twin
|
|
2654
|
+
|
|
2655
|
+
# cand in h_to_remove
|
|
2656
|
+
c_in_h_to_remove = 0
|
|
2657
|
+
for i in range(_n_h_to_remove):
|
|
2658
|
+
c_in_h_to_remove |= (cand == _h_to_remove[i])
|
|
2659
|
+
|
|
2660
|
+
while (cand != h) and (cand != -1) and (c_in_h_to_remove):
|
|
2661
|
+
cand = self._chalfedges[self._chalfedges[cand].prev].twin
|
|
2662
|
+
|
|
2663
|
+
# cand in h_to_remove
|
|
2664
|
+
c_in_h_to_remove = 0
|
|
2665
|
+
for i in range(_n_h_to_remove):
|
|
2666
|
+
c_in_h_to_remove |= (cand == _h_to_remove[i])
|
|
2667
|
+
|
|
2668
|
+
if (cand == h) or (cand == -1) or (c_in_h_to_remove):
|
|
2669
|
+
|
|
2670
|
+
#print(f'h: {h}, {self._halfedges[h]}')
|
|
2671
|
+
# Check if this is a single vertex on it's own
|
|
2672
|
+
vtx_halfedges = np.argwhere(self._halfedges['vertex']==hvx).squeeze()
|
|
2673
|
+
if np.all([vtx_h in h_to_remove for vtx_h in vtx_halfedges]):
|
|
2674
|
+
print('All incident halfedges are being removed, remove vertex too')
|
|
2675
|
+
ids_to_remove = np.concatenate([ids_to_remove, [hvx, ]])
|
|
2676
|
+
else:
|
|
2677
|
+
print('ERROR: could not find a suitable replacement vertex halfedge, mesh will be singular')
|
|
2678
|
+
else:
|
|
2679
|
+
self._cvertices[hvx].halfedge = cand
|
|
2680
|
+
|
|
2681
|
+
|
|
2682
|
+
# remove twin references
|
|
2683
|
+
for h in h_to_remove:
|
|
2684
|
+
ht = self._chalfedges[h].twin
|
|
2685
|
+
if ht >=0:
|
|
2686
|
+
self._chalfedges[ht].twin = -1
|
|
2687
|
+
|
|
2688
|
+
self._halfedges[h_to_remove] = -1
|
|
2689
|
+
self._halfedge_vacancies.extend(h_to_remove)
|
|
2690
|
+
|
|
2691
|
+
self._vertices[ids_to_remove] = -1
|
|
2692
|
+
self._vertex_vacancies.extend(ids_to_remove)
|
|
2693
|
+
|
|
2694
|
+
self._faces[f_to_remove] = -1
|
|
2695
|
+
self._face_vacancies.extend(f_to_remove)
|
|
2696
|
+
|
|
2697
|
+
|
|
2698
|
+
|
|
2699
|
+
def to_stl(self, filename):
|
|
2700
|
+
"""
|
|
2701
|
+
Save list of triangles to binary STL file.
|
|
2702
|
+
|
|
2703
|
+
Parameters
|
|
2704
|
+
----------
|
|
2705
|
+
filename : string
|
|
2706
|
+
File name to write
|
|
2707
|
+
|
|
2708
|
+
Returns
|
|
2709
|
+
-------
|
|
2710
|
+
None
|
|
2711
|
+
"""
|
|
2712
|
+
from PYME.IO.FileUtils import stl
|
|
2713
|
+
|
|
2714
|
+
dt = np.dtype([('normal', '3f4'), ('vertex0', '3f4'), ('vertex1', '3f4'),
|
|
2715
|
+
('vertex2', '3f4'), ('attrib', 'u2')])
|
|
2716
|
+
|
|
2717
|
+
triangles_stl = np.zeros(self.faces.shape[0], dtype=dt)
|
|
2718
|
+
triangles_stl['vertex0'] = self.vertices[self.faces[:, 0]]
|
|
2719
|
+
triangles_stl['vertex1'] = self.vertices[self.faces[:, 1]]
|
|
2720
|
+
triangles_stl['vertex2'] = self.vertices[self.faces[:, 2]]
|
|
2721
|
+
triangles_stl['normal'] = self.face_normals
|
|
2722
|
+
|
|
2723
|
+
stl.save_stl_binary(filename, triangles_stl)
|
|
2724
|
+
|
|
2725
|
+
def to_ply(self, filename, colors=None):
|
|
2726
|
+
"""
|
|
2727
|
+
Save a list of triangles and their vertex colors to a PLY file.
|
|
2728
|
+
We assume colors are a Nx3 array where N is the number of vertices.
|
|
2729
|
+
"""
|
|
2730
|
+
from PYME.IO.FileUtils import ply
|
|
2731
|
+
|
|
2732
|
+
# Construct a re-indexing for non-negative vertices
|
|
2733
|
+
live_vertices = np.flatnonzero(self._vertices['halfedge'] != -1)
|
|
2734
|
+
new_vertex_indices = np.arange(live_vertices.shape[0])
|
|
2735
|
+
vertex_lookup = np.zeros(self._vertices.shape[0], dtype=np.int)
|
|
2736
|
+
|
|
2737
|
+
vertex_lookup[live_vertices] = new_vertex_indices
|
|
2738
|
+
|
|
2739
|
+
# Grab the faces and vertices we want
|
|
2740
|
+
faces = vertex_lookup[self.faces]
|
|
2741
|
+
vertices = self._vertices['position'][live_vertices]
|
|
2742
|
+
|
|
2743
|
+
ply.save_ply(filename, vertices, faces, colors)
|
|
2744
|
+
|
|
2745
|
+
def to_threejs(self, color='red'):
|
|
2746
|
+
import pythreejs as p3j
|
|
2747
|
+
|
|
2748
|
+
t = self.vertices[self.faces].reshape(-1, 3)
|
|
2749
|
+
n = self.vertex_normals[self.faces].reshape(-1, 3)
|
|
2750
|
+
|
|
2751
|
+
m = p3j.Mesh(p3j.BufferGeometry(attributes={'position': p3j.BufferAttribute(t.astype('f4')),
|
|
2752
|
+
'index': p3j.BufferAttribute(np.arange(t.shape[0], dtype='uint32')),
|
|
2753
|
+
'normal': p3j.BufferAttribute(n.astype('f4'))}),
|
|
2754
|
+
material=p3j.MeshPhongMaterial(color=color))
|
|
2755
|
+
|
|
2756
|
+
return m
|
|
2757
|
+
|
|
2758
|
+
def _repr_mimebundle_(self, **kwargs):
|
|
2759
|
+
try:
|
|
2760
|
+
return self.to_threejs()._repr_mimebundle_(**kwargs)
|
|
2761
|
+
except ImportError:
|
|
2762
|
+
return {'text/plain': repr(self) + '\nInstall pythreejs to display in notebook'}
|
|
2763
|
+
|
|
2764
|
+
|
|
2765
|
+
def plot(self, figure=None, axis=None):
|
|
2766
|
+
"""
|
|
2767
|
+
Display surface using matplotlib. Useful for quick use in a notebook (or similar) when a wx GUI is not
|
|
2768
|
+
available. NOTE: will be slower than displaying through PYMEVis
|
|
2769
|
+
|
|
2770
|
+
Parameters
|
|
2771
|
+
----------
|
|
2772
|
+
figure : [optional] a matplotlib figure instance
|
|
2773
|
+
axis : [optional] a matplotlib Axes3D instance
|
|
2774
|
+
|
|
2775
|
+
Returns
|
|
2776
|
+
-------
|
|
2777
|
+
|
|
2778
|
+
"""
|
|
2779
|
+
import matplotlib.pyplot as plt
|
|
2780
|
+
|
|
2781
|
+
if axis is None:
|
|
2782
|
+
if figure is None:
|
|
2783
|
+
figure = plt.figure()
|
|
2784
|
+
|
|
2785
|
+
axis = figure.add_subplot(projection='3d')
|
|
2786
|
+
|
|
2787
|
+
return axis.plot_trisurf(self.vertices[:,0], self.vertices[:,1], self.faces, self.vertices[:,2])
|